Rust Programming language

A comprehensive beginner's guide to Rust

3nd edition

2021

By Claudia Alves

"Programming isn't about what you know; it's about what you can figure out . ” - Chris Pine

.

Memlnc.com

WHY LEARN A NEW PROGRAMMING LANGUAGE?..............................................................12

WHERE RUST SHINES............................................................................................................13

SETTING UP...........................................................................................................................15

CHAPTER I ________________________________________________________________22

FIRST STEPS............................................................................................................................22

HELLO WORLD! .....................................................................................................................23

HELLO, CARGO! .....................................................................................................................27

MIGRATING IN CHARGE.........................................................................................................28

A NEW PROJECT.....................................................................................................................32

CHAPTER II ________________________________________________________________35

LEARN RUST............................................................................................................................35

INITIAL SETUP..........................................................................................................................35

PROCESSING A RIDDLE ATTEMPT............................................................................................37

GENERATING A SECRET NUMBER...........................................................................................45

COMPARING RIDDLES.............................................................................................................51

ITERATION...............................................................................................................................58

COMPLETED! ..........................................................................................................................68

RUST WITHIN OTHER LANGUAGES..........................................................................................90

THE PROBLEM.........................................................................................................................91

A RUST LIBRARY......................................................................................................................93

RUBY................................................................................................................................... .....97

PYTHON...................................................................................................................................99

NODE.JS.................................................................................................................................100

$ NPM INSTALL FFI................................................................................................................100

CHAPTER III______________________________________________________________102

RUST CASH..........................................................................................................................,102

THE STACK AND THE MOUND..............................................................................................103

MEMORY MANAGEMENT.....................................................................................................103

THE BATTERY........................................................................................................................103

THE MOUND.........................................................................................................................108

ARGUMENTS AND BORROWING...........................................................................................111

A COMPLEX EXAMPLE...........................................................................................................112

WHAT DO OTHER LANGUAGES DO? ....................................................................................118

WHICH TO USE......................................................................................................................119

EXECUTION TIME EFFICIENCY. .............................................................................................119

SEMANTIC IMPACT...............................................................................................................119

TESTS...........................................................................................................,………………………120

THE TEST ATTRIBUTE.............................................................................................................120

THE TESTS MODULE..............................................................................................................127

THE TESTS DIRECTORY...........................................................................................................129

DOCUMENTATION TESTS......................................................................................................131

[CFG (TEST)] ..........................................................................................................................131

CONDITIONAL COMPILATION................................................................................................133

CFG_ATTR..............................................................................................................................135

CFG! ......................................................................................................................................135

DOCUMENTATION................................................................................................................135

ABOUT RUSTDOC..................................................................................................................135

DOCUMENTING SOURCE CODE.............................................................................................136

WRITING DOCUMENTATION COMMENTS............................................................................137

SPECIAL SECTIONS.................................................................................................................138

FN FOO () {}...........................................................................................................139

FN FOO () {}...........................................................................................................139

FN FOO () {}...........................................................................................................140

FN FOO () {}...........................................................................................................140

FN FOO () {}...........................................................................................................141

FN FOO () {}...........................................................................................................141

DOCUMENTING MACROS......................................................................................144

[MACRO_EXPORT] ................................................................................................144

FN MAIN () {}...........................................................................................................145

FN FOO () {}...........................................................................................................145

FN FOO () {}...........................................................................................................146

FN FOO () {}...........................................................................................................146

DOCUMENTATION FEEDBACK STYLE…………………………………………………………………148

OTHER DOCUMENTATION.....................................................................................148

FN FOO () {}...........................................................................................................149

DOC ATTRIBUTES...................................................................................................149

RE-EXPORTS...........................................................................................................150

CONTROLLING HTML..............................................................................................150

GENERATION OPTIONS...........................................................................................151

SAFETY NOTE...........................................................................................................151

CONSUMERS...........................................................................................................154

ITERATORS...............................................................................................................157

ITERATOR ADAPTERS................................................................................................158

CONCURRENCE..........................................................................................................159

THREADS...................................................................................................................160

MUTABLE STATE SHARED SAFE.................................................................................161

CHANNELS................................................................................................................166

PANICS......................................................................................................................168

ERROR HANDLING.....................................................................................................169

FAIL VS. PANIC...........................................................................................................169

HANDLING ERRORS WITH OPTION AND RESULT…………………………………………………….173

UNRECOVERABLE ERRORS WITH PANIC! ..................................................................175

PROMOTING PANIC FAILURES....................................................................................175

USING TRY! .................................................................................................................176

FOREIGN / EXTERNAL FUNCTION INTERFACE.............................................................179

INTRODUCTION...........................................................................................................180

CREATING A SECURE INTERFACE.................................................................................182

DESTROYERS................................................................................................................186

CALLBACKS FROM C CODE TO RUST FUNCTIONS……………………………………………………..187

AIMING CALLBACKS AT RUST OBJECTS…………………………………………………………………….188

ASYNCHRONOUS CALLBACKS.......................................................................................191

LINK...........................................................................................................,,……………….191

UNSAFE BLOCKS...........................................................................................................193

ACCESSING EXTERNAL GLOBALS...................................................................................193

FOREIGN CALLING CONVENTIONS................................................................................195

INTEROPERABILITY WITH EXTERNAL CODE……………………………………………………………….196

THE "NULL POINTER OPTIMIZATION"............................................................................197

CALLING CODING RUST FROM C.....................................................................................197

FFI AND PANICS..............................................................................................................198

BORROW........................................................................................................................199

ASREF.............................................................................................................................200

WHICH ONE SHOULD YOU USE? ....................................................................................201

OVERVIEW..........................................................................................................................201

CHOOSING A VERSION.......................................................................................................202

HELPING THE ECOSYSTEM THROUGH CI............................................................................202

CHAPTER IV _____________________________________________________________202

SYNTAX AND SEMANTICS...................................................................................................202

VARIABLE LINKS..................................................................................................................203

FEATURES............................................................................................................................207

EXPRESSIONS VS. SENTENCES.............................................................................................210

EARLY RETURNS...................................................................................................................211

DIVERGENT FUNCTIONS.......................................................................................................212

FUNCTION POINTERS...........................................................................................................213

PRIMITIVE TYPES..................................................................................................................214

BOOLEAN.............................................................................................................................214

CHAR....................................................................................................................................214

NUMERICAL TYPES...............................................................................................................215

SIGNED AND UNSIGNED.......................................................................................................216

FIXED SIZE TYPES..................................................................................................................216

VARIABLE SIZE TYPES...........................................................................................................216

FLOATING POINT TYPES.......................................................................................................216

ARRANGEMENTS.................................................................................................................216

SLICES..................................................................................................................................217

TUPLES.................................................................................................................................218

INDEXED IN TUPLES.............................................................................................................220

FEATURES.............................................................................................................................220

COMMENTS..........................................................................................................................221

IF...........................................................................................................................................222

CYCLES...........................................................................................................................224

LOOP..............................................................................................................................224

WHILE.............................................................................................................................225

FOR.................................................................................................................................226

ENDING THE ITERATION EARLY.......................................................................................228

LOOP TAGS......................................................................................................................229

BELONGING.....................................................................................................................230

GOAL...............................................................................................................................230

BELONGING.....................................................................................................................231

MOTION SEMANTICS.......................................................................................................232

THE DETAILS....................................................................................................................233

COPY TYPES.....................................................................................................................234

MORE THAN BELONGING................................................................................................236

REFERENCES AND LOAN..................................................................................................237

GOAL................................................................................................................................237

LOAN................................................................................................................................238

REFERENCES & MUT.........................................................................................................240

THE RULES..........................................................................................................................242

THINKING ABOUT SCOPES.................................................................................................242

PROBLEMS THAT THE LOAN PREVENTS.............................................................................244

ITERATOR INVALIDATION...................................................................................................244

USE AFTER RELEASE...........................................................................................................246

LIFE TIMES..........................................................................................................................249

GOAL..................................................................................................................................250

LIFE TIMES..........................................................................................................................250

IN STRUCT S.......................................................................................................................252

IMPL BLOCKS......................................................................................................................253

MULTIPLE LIFE TIME...........................................................................................................254

THINKING ABOUT SCOPES...................................................................................................255

'STATIC.................................................................................................................................257

LIFE TIMES ELISION..............................................................................................................257

EXAMPLES...........................................................................................................................258

MUTABILITY.........................................................................................................................261

INTERNAL MUTABILITY VS. EXTERIOR MUTABILITY…………………………………………………………262

FIELD LEVEL MUTABILITY.....................................................................................................264

STRUCTURES........................................................................................................................265

UPDATE SYNTAX...................................................................................................................268

TUPLE STRUCTURES (TUPLE STRUCTS) ................................................................................269

UNIT-LIKE STRUCTS...............................................................................................................271

ENUMERATIONS...................................................................................................................271

CONSTRUCTORS AS FUNCTIONS...........................................................................................273

MATCH..................................................................................................................................274

MAKING MATCH IN ENUMS..................................................................................................276

VECTOR.................................................................................................................................277

ACCESSING ELEMENTS..........................................................................................................278

ITERATING.............................................................................................................................278

CHARACTER STRINGS............................................................................................................279

INDEXED................................................................................................................................281

SLICING..................................................................................................................................282

CONCATENATION..................................................................................................................283

GENERIC................................................................................................................................283

GENERIC FUNCTIONS............................................................................................................285

GENERIC STRUCTURES.......................................................................................................286

TRAITS................................................................................................................................287

TRAIT LIMITS FOR GENERIC FUNCTIONS............................................................................289

TRAIT LIMITS FOR GENERIC STRUCTURES...........................................................................292

RULES FOR THE IMPLEMENTATION OF TRAITS...................................................................294

MULTIPLE TRAIT LIMITS.......................................................................................................296

THE WHERE CLAUSE.............................................................................................................297

DEFAULT METHODS..............................................................................................................300

HERITAGE..............................................................................................................................302

DROP.....................................................................................................................................303

IF LET.....................................................................................................................................305

WHILE LET.............................................................................................................................307

TRAIT OBJECTS......................................................................................................................308

BASES....................................................................................................................................308

STATIC DISPATCH..................................................................................................................309

DYNAMIC DISPATCH.............................................................................................................311

WHY POINTERS? ...................................................................................................................313

REPRESENTATION..................................................................................................................313

OBJECT SECURITY. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . …………...318

CLOSURES..............................................................................................................................319

SYNTAX..................................................................................................................................319

CLOSURES AND ITS SURROUNDINGS....................................................................................320

CLOSURES MOVE...................................................................................................................323

IMPLEMENTATION OF CLOSURES.........................................................................................324

RECEIVING CLOSURES AS ARGUMENTS................................................................................326

FUNCTION POINTERS AND CLOSURES. . . . . . . . . . . . . . . . . . ... . . .. . . . . . . . . . . . . ………….. .328

RETURNING CLOSURES. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 329

Why learn a new Programming Language?

As Einstein might have said, "As gentle as possible, but no gentler.". There is a lot of new stuff to learn here, and it's different enough to require some rearrangement of your mental furniture. By 'gentle' I mean that the features are presented practically with examples; as we encounter difficulties, I hope to show how Rust solves these problems. It is important to understand the problems before the solutions make sense. To put it in flowery language, we are going for a hike in hilly country and I will point out some interesting rock formations on the way, with only a few geology lectures. There will be some uphill but the view will be inspiring; the community is unusually pleasant and happy to help. There is the Rust Users Forum and an active subreddit which is unusually well-moderated. The FAQ is a good resource if you have specific questions.

First, why learn a new programming language? It is an investment of time and energy and that needs some justification. Even if you do not immediately land a cool job using that language, it stretches the mental muscles and makes you a better programmer. That seems a poor kind of return-on-investment but if you're not learning something genuinely new all the time then you will stagnate and be like the person who has ten years of experience in doing the same thing over and over.

Where Rust Shines

Rust is a statically and strongly typed systems programming language. statically means that all types are known at compile-time, strongly means that these types are designed to make it harder to write incorrect programs. A successful compilation means you have a much better guarantee of correctness than with a cowboy language like C. systems means generating the best possible machine code with full control of memory use. So the uses are pretty hardcore: operating systems, device drivers and embedded systems that might not even have an operating system. However, it's actually a very pleasant language to write normal application code in as well.

The big difference from C and C++   is that Rust is safe by default; all memory accesses are checked. It is not possible to corrupt memory by accident.

The unifying principles behind Rust are:

-    strictly enforcing safe borrowing of data

-    functions, methods and closures to operate on data

-    tuples, structs and enums to aggregate data

-    pattern matching to select and destructure data

-    traits to define behaviour on data

There is a fast-growing ecosystem of available libraries through Cargo but here we will concentrate on the core principles of the language by learning to use the standard library. My advice is to write lots of small programs, so learning to use rustc directly is a core skill. When doing the examples in this tutorial I defined a little script called rrun which does a compilation and runs the result:

rustc $1.rs && ./$1

Setting Up

This tutorial assumes that you have Rust installed locally. Fortunately this is very straightforward.

$ curl https://sh.rustup.rs -sSf | sh

$ rustup component add rust-docs

I would recommend getting the default stable version; it's easy to download unstable versions later and to switch between.

This gets the compiler, the Cargo package manager, the API documentation, and the Rust Book. The journey of a thousand miles starts with one step, and this first step is painless.

rustup is the command you use to manage your Rust installation. When a new stable release appears, you just have to say rustup update to upgrade. rustup doc will open the offline documentation in your browser.

You will probably already have an editor you like, and basic Rust support is good. I'd suggest you start out with basic syntax highlighting at first, and work up as your programs get larger.

Personally I'm a fan of Geany which is one of the few editors with Rust support out-of-the-box; it's particularly easy on Linux since it's available through the package manager, but it works fine on other platforms.

The main thing is knowing how to edit, compile and run Rust programs. You learn to program with your fingers; type in the code yourself, and learn to rearrange things efficiently with your editor.

Is Rust a language that you would be interested in? Let's examine a few small code examples that demonstrate some of its strengths.

The main concept that makes Rust unique is called 'ownership'. Consider this little example:

fn main ( ) {

let mut x = vec! [ "Hello" , "world" ];

}

This program creates a variable called x . The value of this variable is Vec <T> , a 'vector', which we create through a macro defined in the standard library. This macro is called vec , macros are invoked with a ! . All this following a general principle in Rust: make things explicit. Macros can do significantly more complex things than function calls, which is why they are visually different. The ! It also helps parsing, making writing tools easier, which is also important.

We have used mut to make x mutable: In Rust the variables are immutable by default. Later in this example we will be mutating this vector.

It's important to mention that we don't need a type annotation here: while Rust is statically typed, we don't need to explicitly annotate the type. Rust has type inference to balance the power of static typing with the verbosity of type annotations.

Rust prefers memory allocation from the stack than from the mound: x is put directly on the stack. However, the Vec <T> type allocates space for the vector elements on the mound. If you are not familiar with this distinction you can ignore it for now or take a look at 'The Stack and the Mound' . Rust as a system programming language gives you the ability to control how memory is allocated, but how we are getting started is not that relevant.

Earlier we mentioned that 'membership' is a new key concept in Rust. In Rust terminology, x is the 'owner' of the vector. This means that when x goes out of scope, the memory allocated to the vector will be freed. This is done deterministically by the Rust compiler , without the need for a mechanism like a garbage collector. In other words, in Rust, you don't make calls to functions like malloc and free explicitly: the compiler statically determines when memory needs to be allocated or freed, and inserts those calls for you. To err is human, but compilers never forget.

Let's add another line to our example:

fn main ( ) {

let mut x = vec! [ "Hello" , "world" ];

let y = & x [ 0 ];

}

We have introduced another variable, and . In this case, y is a 'reference' to the first element of the vector. References in Rust are similar to pointers in other languages, but with additional security checks at compile time. References interact with the membership system through 'borrowing' , they borrow what they are targeting, rather than owning it. The difference is that when the reference goes out of scope, the underlying memory will not be freed. If that is the case we would be releasing the same memory twice, which is bad.

Let's add a third line. This line looks innocent but causes a compilation error:

fn main ( ) {

let mut x = vec! [ "Hello", "world"];

let y = & x [ 0];

x.push ("foo");

}

push is a method in vectors that adds an element to the end of the vector. When we try to compile the program we get an error:

error: cannot borrow `x` as mutable because it is also borrowed as immutable

x.push ("foo");

^

note: previous borrow of `x` occurs here; the immutable borrow prevents

subsequent moves or mutable borrows of `x` until the borrow ends

let y = & x [ 0];

^

note: previous borrow ends here

fn main ( ) {

}

^

Whoa! The Rust compiler can sometimes provide well-detailed errors and this time one of them. As the error explains, while we make the variable mutable we cannot call push . This is because we already have a reference to an element of the vector, y . Mutating something while there is a reference to it is dangerous, because we can invalidate the reference. In this specific case, when we create the vector, we have only allocated space for two elements. Adding a third party would mean allocating a new memory segment for all items, copying all previous values, and updating the internal pointer to that memory. All of that is fine. The problem is that and would not be updated, generating a 'hanging pointer'. Which is wrong. Any use of y would be a mistake in this case, and the compiler has warned us against it.

So how do we solve this problem? There are two approaches that we could take. The first is to make a copy instead of a reference:

fn main ( ) {

let mut x = vec! [ "Hello" , "world" ];

let y = x [ 0 ] .clone ();

x.push ( "foo" );

}

Rust has motion semantics by default , so if we want to make a copy of some data, we call the clone () method . In this example, y is no longer a reference to the vector stored in x , but a copy of its first element, "Hello" . Because we don't have a reference, our push ( ) works perfectly.

If we really want a reference, we need another option: make sure our reference goes out of scope before we try to do the mutation. In this way:

fn main ( ) {

let mut x = vec! [ "Hello" , "world" ];

{

let y = & x [ 0 ];

}

x.push ( "foo" );

}

With the additional pair of keys we have created an internal scope. and it will go out of scope before we call push ( ) , so no problem.

This membership concept is not only good at preventing dangling pointers, but an entire set of issues, such as iterator invalidation, concurrency, and more.

Chapter I

First steps

This first section of the book will walk you through Rust and his tools. First, we will install Rust. Then the classic program 'Hello World'. Finally, we'll talk about Cargo, Rust's package manager and build system.

Hello World!

Now that you have installed Rust, let's write your first program. It is tradition that your first program in any language is one that prints the text "Hello, world!" to the screen. The good thing about starting with such a simple program is that you verify not only that the compiler is installed, but that it is working. Printing information to the screen is a very common thing.

The first thing we must do is create a file where we can put our code. I like to create a projects directory in my user directory and keep all my projects there. Rust doesn't care where the code resides.

This leads to another issue to be clarified: this guide will assume that you have a basic familiarity with the command line. Rust does not demand anything regarding your editing tools or where your code lives. If you prefer an IDE to the command line interface, you should probably take a look at SolidOak , or wherever the plugins are for your favorite IDE. There are a number of extensions of varying quality under development by the community. The team behind Rust also publishes plugins for various publishers . Setting up your editor or IDE is beyond the objectives of this tutorial, check the documentation for your specific configuration.

With that said, let's create a directory in our project directory.

$ mkdir ~ / projects

$ cd ~ / projects

$ mkdir hello_world

$ cd hello_world

If you're on Windows and you're not using PowerShell, the ~ might not work. Consult the specific documentation for your terminal for more details.

Let's create a new source code file. We will call our file main.rs. Rust files end with the extension .rs . If you are using more than one word in your file name, use a sub-hyphen: hello_world.rs instead of helloworld.rs .

Now that you have your file open write this in:

fn main ( ) {

println! ( "Hello, world!" );

}

Save the changes to the file, and write the following in your terminal window:

$ rustc main.rs

$. / main # or main.exe in Windows

Hello World!

Success! Now let's see what has happened in detail.

fn main ( ) {

}

These lines define a function in Rust. The main function is special: it is the beginning of every Rust program. The first line says: "I am declaring a function called main which receives no arguments and returns nothing." If there were arguments, they would be enclosed in parentheses ( ( and ) ), and since we are not returning anything from this function, we can omit the return type entirely. We will come to this later.

You will also notice that the function is wrapped in curly braces ( { and } ). Rust requires these keys delimiting all function bodies. It is also considered a good style to place the opening brace on the same line as the function declaration, with an intermediate space.

The following is this line:

println! ( "Hello, world!" );

This line does all the work in our little program. There are a number of details that are important here. The first is that it is indented with four spaces, not tabs. Please configure your editor to insert four spaces with the tab key. We provide some sample configurations for various editors .

The second point is the println! ( ) Part . This is calling a Rust macro , which is how metaprogramming is done in Rust. If this were a function instead, it would look like this: println ( ) . For our purposes, we need not worry about this difference. Just know that sometimes you will see ! , and that this means that you are calling a macro instead of a normal function. Rust implements println! as a macro instead of a function for good reason, but that's an advanced topic. One last thing to mention: Rust macros are different from C macros, if you've used them. Don't be scared of using macros. We'll get to the details eventually, for now you just have to trust us.

Then "Hello world!" it is a character string. Character strings are a surprisingly complex topic in system programming languages, and this specifically is a 'statically assigned' character string. If you'd like to read about memory allocation, take a look at the stack and the mound , but for now you don't need to if you don't want to. We pass this string as an argument to println! who in turn prints the character string to the screen. Easy!

Finally, the line ends with a semicolon ( ; ). Rust is an expression-oriented language, which means that most things are expressions, rather than statements. The ; it is used to indicate that the expression has ended, and that the next one is ready to start. Most lines of code in Rust end with a ; .

Finally, compile and run our program. We can compile with our rustc compiler by passing it the name of our source file:

$ rustc main.rs

This is similar to gcc or clang , if you come from C or C . Rust will output an executable binary. You can see it with ls : 

$ ls

main main.rs

Or in Windows:

$ dir

main.exe main.rs

There are two files: our source code, with the extension .rs , and the executable ( main.exe in Windows, main in others)

$. / main  # or main.exe in Windows

The above prints our text Hello world! to our terminal.

If you're coming from a dynamic language like Ruby, Python, or Javascript, you're probably not used to seeing these two steps as separate. Rust is a compiled language, which means you can compile your program, give it to someone else, and they don't need to have Rust installed. If you give someone a .rb or .py or .js file , they need to have a Ruby / Python / JavaScript implementation, but you only need one command for both of them, compile and run your program. It's all about balancing advantages / disadvantages in language design, and Rust has chosen.

Congratulations, you have officially written a Rust program. That makes you a Rust programmer! Welcome. ������

Next I would like to introduce you to another tool, Cargo, which is used to write Rust programs for the real world. Just using rustc is fine for simple things, but as your project grows, you will need something to help you manage all the options it has, as well as making it easy to share the code with other people and projects.

Hello, Cargo!

Cargo is a tool that Rusteros use to help them manage their Rust projects. Cargo is currently in pre-1.0 status, and as a result is still a work in progress. However it is already good enough for many Rust projects, and it is assumed that Rust projects will use Cargo from the beginning.

Cargo manages three things: compiling your code, downloading the dependencies your code needs, and compiling those dependencies. In the first instance, your code will not have any dependency, that is why we will be using only the first part of the Cargo functionality. Eventually we will add more. Because we started using Cargo from the beginning it will be easy to add later.

If you have installed Rust through the official installers then you should have Cargo. If you have installed Rust in any other way, you could take a look at Cargo's README for specific instructions on how to install it.

Migrating in Charge

Let's convert Hello World in Charge.

To load our project, we need two things: Create a Cargo.toml configuration file , and put our source file in the right place. Let's do that part first:

$ mkdir src

$ mv main.rs src / main.rs

Note that because we are creating an executable, use main.rs . If we wanted to create a library, you should use lib.rs . Customized locations for the entry point can be specified with a [ [[lib]] or [[bin]] ] crates-custom key in the TOML file described below.

Cargo expects your source code files to reside in the src directory . This leaves the root level for other things, like READMEs, licensing information, and anything unrelated to your code. Cargo helps us keep our projects nice and orderly. A place for everything, and everything in its place.

Here is our configuration file:

$ editor Cargo.toml

Make sure you have this correct name: you need the capital C !

Put this inside:

[package]

name = "hello_world"

version = "0.0.1"

authors = [ "Your name <your@example.com> " ]

This file is in TOML format . Let us explain the same:

TOML's goal is to be a minimal configuration format that is easy to read due to obvious semantics. TOML is designed to unambiguously map to a hash table. TOML should be easy to convert to data structures in a wide variety of languages.

TOML is very similar to INI, but with some extra niceties.

Once you have this file in its place, we should be ready to compile! Try this:

$ build charge

Compiling hello_world v0. 0.1 (file: /// home / yourname / projects / hello_world)

$. / target / debug / hello_world

Hello World!

Bam! We build our project with build charge , and run it with . / target / debug / hello_world . We can do the two steps in one with run charge :

$ charge run

Running `target / debug / hello_world`

Hello World!

Note that we did not rebuild the project this time. Cargo determined that we hadn't changed the source file, so I just run the binary. If we had made a modification, we should have seen it doing the two steps:

$ charge run

Compiling hello_world v0. 0.1 (file: /// home / yourname / projects / hello_world)

Running `target / debug / hello_world`

Hello World!

This has not contributed much to us beyond our simple use of rustc , but think about the future: when our projects get more complicated, we will need to do more to get all the parts to compile correctly. With Cargo, as our project grows, we simply run cargo build , and everything will work correctly.

When our project is finally ready for release, you can use cargo build --release to compile it with optimizations.

You may also have noticed that Cargo has created a new file: Cargo.lock .

[root]

name = "hello_world"

version = "0.0.1"

This file is used by Cargo to keep track of the dependencies used in your application. For now, we don't have any, and it's a little scattered. You should never need to touch this file on your own, just let Cargo handle it.

That's it! We have successfully built hello_world with Cargo. Although our program is simple, it is using much of the real tools that you will use for the rest of your career with Rust. You can assume that to start virtually any Rust project you will do the following:

$ git clone someturl.com/foo

$ cd foo

$ build charge

A New Project

You don't have to go through that entire process every time you want to start a new project! Cargo has the ability to create a template directory in which you can start developing immediately.

To start a new project with Cargo, we use cargo new :

$ cargo new hello_world –bin

We are passing --bin because we are creating a binary program: if we were creating a library, we would skip it.

Let's take a look at what Cargo has generated for us:

$ cd hello_world

$ tree.

.

├── Cargo.toml

└── src

└── main.rs

1 directory, 2 files

If you don't have the tree command installed, you could probably get it using your distribution's package handler. It is not necessary, but it is certainly useful.

This is all you need to get started. Let's first look at our Cargo.toml :

[package]

name = "hello_world"

version = "0.0.1"

authors = [ "Your Name <your@example.com>" ]

I loaded this file with default values ​​based on the arguments you provided and your global git settings . You might also notice that Cargo has initialized the hello_world directory as a git repository .

Here is the content of src / main.rs :

fn main ( ) {

println! ( "Hello, world!" );

}

Cargo has generated a "Hello world!" for us, you're ready to start rubbing elbows. Cargo has its own guide which covers all its features in much more depth.

Now that we have learned the tools, let's actually start learning more about Rust as a language. This is the base that will serve you well for the rest of your time with Rust.

You have two options: Immerse yourself in a project with ' Learn Rust ', or start from the bottom and work upwards with ' Syntax and Semantics '. More experienced system programmers will probably prefer 'Learn Rust', while those from dynamic languages ​​might also enjoy it. Different people learn differently! Choose what works best for you.

Chapter I

Learn Rust

Welcome! This section has a few tutorials that will teach you Rust through project building. You'll get an overview, but we'll also take a look at the details.

If you prefer a more bottom-up experience, check out Syntax and Semantics .

Guessing Game

For our first project, we will implement a classic programming problem for beginners: a guessing game. How the game works: Our program will generate a random integer between one and one hundred. It will ask us to introduce a hunch. After having provided our number, it will tell us if we were far below and far above. Once we guess the correct number, you will congratulate us. Sounds good?

Initial setup

Let's create a new project. Go to your project directory. Remember how we created our directory structure and a Cargo.toml for hello_world ? I have a command that does that for us. Let's try it:

$ cd ~ / projects

$ charge new riddles --bin

$ cd riddles

We pass the name of our project in charge new , along with the flag --bin , because we are creating a binary, instead of a library.

Take a look at the generated Cargo.toml :

[package]

name = "riddles"

version = "0.1.0"

authors = [ "Your Name <your@example.com>" ]

Cargo obtains this information from your environment. If it is not correct, correct it.

Finally, Cargo has generated a 'Hello, world!' for us. Take a look at src / main.rs :

fn main () {

println! ( "Hello, world!" );

}

Let's try to compile what Cargo has provided us:

$ build charge

Compiling riddles v0.1.0 (file: /// home / you / projects / riddles)

Excellent! Open your src / main.rs again. We will be writing all our code in this file.

Before continuing, let me show you one more command from Cargo: run . cargo run is a kind of cargo build , but with the difference that it also executes the binary produced. Let's try it:

$ charge run

Compiling riddles v0. 1.0 (file: /// home / you / projects / riddles)

Running `target / debug / riddles`

Hello World!

Great! The run command is very useful when you need to quickly iterate through a project. Our game is one of those projects, we will need to quickly test each iteration before moving on to the next.

Processing a Riddle Attempt

Let's try it! The first thing we need to do for our guessing game is allow our player to enter a guess attempt. Put this in your src / main.rs :

use std :: io;

fn main () {

println! ("Guess the number!");

println! ("Please enter your hunch.");

let mut hunch = String :: new ();

io :: stdin (). read_line (& mut hunch)

.okay()

.expect ("Failed to read line");

println! ("Your hunch was: {}", hunch);

}

There is a lot here! Let's try to go through it, piece by piece.

use std :: io;

We will need to receive input from the user, and then print the result as output. Because of this we need the io library from the standard library. Rust only matters a few things for all programs, this set of things is called a 'prelude' . If it is not in the prelude you will have to call it directly through use .

fn main () {

As you have seen previously, the main () function is the entry point to your program. The fn syntax declares a new function, the () s indicate that there are no arguments, and { the body of the function begins. Because we don't include a return type, it is assumed to be () an empty tuple .

println! ("Guess the number!");

println! ("Please enter your hunch.");

We previously learned that println! () Is a macro that prints a string of characters to the screen.

let mut hunch = String :: new ();

Now we are getting interesting! There are a lot of things going on in this little line. The first things to note is that it is a let statement , used to create variables. It has the form:

let foo = bar;

This will create a new variable called foo , and bind it to the value bar . In many languages ​​this is called a 'variable' but Rust variables have a few tricks up their sleeve.

For example, they are immutable by default. This is why our example uses mut : this does a mutable binding, rather than immutable. let doesn't just take a name from the left side, let accepts a ' pattern '. We will use the patterns a little later. It is sufficient for now to use:

let foo = 5 ; // immutable.

let mut bar = 5 ; // mutable

Ah, // start a comment, until the end of the line. Rust ignores everything in comments

So we know that let mut hunch will introduce a mutable binding called hunch , but we have to look on the other side of = to know what it is being associated with: String :: new () .

String is a type of character string, provided by the standard library. A String is a segment of text encoded in UTF-8 capable of growing.

The :: new () syntax uses :: because it is an 'associated function' of a particular type. In other words, it is associated with String itself, rather than with a particular instance of String . Some languages ​​call this a 'static method'.

This function is called new () , because it creates a new empty String . You will find a new () function on many types, because it is a common name for creating a new value of some type.

Let's continue:

io :: stdin (). read_line (& mut hunch)

.okay()

.expect ("Line reading failure");

Another pile! Let's go piece by piece. The first line has two parts. Here is the first one:

io :: stdin ()

Remember how we use use in std :: io in the first line of our program? We are now calling an associated function in std :: io . If you had not used use std :: io , we could have written this line as std :: io :: stdin () .

This particular function returns a handle to the standard input of your terminal. More specifically, a std :: io :: Stdin .

The next part will use that handle to get user input:

.read_line (& mut hunch)

Here, we call the read_line ( ) method on our handle. The methods are similar to the associated functions, but are only available in a particular instance of a type, rather than the type itself. We're also passing an argument to read_line () : & mut hunch .

Remember when we created hunch ? We said it was mutable. However read_line does not accept a String as argument: it accepts a & mut String . Rust has a feature called ' references ', which allows having multiple references to a piece of data, thus reducing the need for copying. References are a complex feature, because one of Rust's strongest selling points is about how easy and safe it is to use references. For now we don't need to know much about those details to finish our program. All we need to know at the moment is that just like let bindings, references are immutable by default. As a consequence we need to write & mut hunch instead of & hunch .

Because read_line () accepts a mutable reference to a character string. Your job is to take what the user enters in the standard input, and put it in a string. Because of this, it takes this string as an argument, and because it must add user input, it needs to be mutable.

We are not done with this line yet. While it is a single line of text, it is just the first part of a complete logical line of code:

.okay()

.expect ("Line reading failure");

When you call a method with the .foo () syntax you can enter a line break and another space. This helps you divide long lines. We could have written:

io :: stdin (). read_line (& mut hunched) .ok (). expect ("Line reading failed");

But that is more difficult to read. So we have divided it into three lines for three method calls. We've already talked about read_line () , but what about ok () and expect () ? Well, we already mentioned that read_line () places the user's input in the & mut String that we provide. But it also returns a value: in this case an io :: Resul t . Rust has a number of types called Result in its standard library: a generic Resul t , and specific versions for sub-libraries, such as io :: Result .

The purpose of those Result is to encode error handling information. Values ​​of type Result have methods defined in them. In this case io :: Result has an ok () method , which translates to 'we want to assume that this value is a successful value. If not, discard the information about the error. ' Why discard the information about the error? For a basic program, we simply want to print a generic error, any problem that means we cannot continue. The ok () method returns a value that has another method defined in: expect () . The expect () metho d takes the value at which it is called and if it is not a successful value, it panics ! with a message that we have provided. A panic! like this it will cause the program to have an abrupt exit (crash), showing this message.

If we remove the calls to those two methods, our program will compile, but we will get a warning:

$ build charge

Compiling riddles v0. 1.0 (file: /// home / you / projects / riddles)

src / main.rs: 10 : 5 : 10 : 44 warning: unused result which must be used, # [warn (unused_must_use)] on by default

src / main.rs: 10      io :: stdin (). read_line (& mut hunch);

^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Rust advises us that we have not used the Result value . This warning comes from a special annotation that io :: Result has . Rust is trying to tell you that you have not handled a possible error. The correct way to suppress the error is to actually write the code for handling errors. Luckily, if we only want to end the program execution if there is a problem, we can use these two small methods. If we could somehow recover from the error, we would do something different, but leave that for a future project.

We only have one line left from this first example:

println! ("Your hunch was: {}", hunch);

}

This line prints the character string in which we save our entry. The {} s are placeholders, that's why we pass riddle as an argument. If there had been multiple {} s, we should have passed multiple arguments:

let x = 5 ;

let y = 10 ;

println! ( "xyy: {} y {}" , x, y);

Easy.

Either way, that's the tour. We can run it with run charge :

$ charge run

Compiling guessing_game v0. 1.0 (file: /// home / you / projects / riddles)

Running `target / debug / riddles`

Guess the number!

Please enter your hunch.

6

Your hunch was: 6

Congratulations! Our first part is over: we can get keyboard input and print it back.

Generating a secret number

Next we need to generate a secret number. Rust does not yet include a random number functionality in the standard library. However, Rust's team provides a crate rand . A 'crate' is a package of Rust code. We have been building a 'crate binaro', which is an executable. rand is a 'crate library', which contains code to be used by other programs.

Using external crates is where Cargo really shines. Before we can write code that uses rand , we must modify our Cargo.toml file . Open it, and add these lines at the end:

[dependencies]

rand = "0.3.0"

The [dependencies] section of Cargo.toml is like the [package] section : everything that follows is part of it, until the next section begins. Cargo uses the dependencies section to know which external crates we depend on, as well as the required versions. In this case we have used version 0.3.0 . Cargo understands Semantic Versioning , which is a standard for writing version numbers. If we wanted to use the latest version we could have used * or a range of versions. The documentation Cargo contains more details.

Now, without changing anything in our code, let's build our project:

$ build charge

Updating registry `https: // github.com / rust-lang / crates.io-index`

Downloading rand v0. 3.8

Downloading libc v0. 1.6

Compiling libc v0. 1.6

Compiling rand v0. 3.8

Compiling riddles v0. 1.0 (file: /// home / you / projects / riddles)

(You could see different versions, of course.)

Lots of more output! Now that we have an external dependency, Cargo downloads the latest versions of everything from the registry, which can copy data from Crates.io . Crates.io is where people in the Rust ecosystem post their open source projects for others to use.

After updating the registry, Cargo checks our dependencies (in [dependencies] ) and downloads them if they do not have them yet. In this case we just said we wanted to rely on rand , and we also got a copy of libc . This is because rand itself depends on libc to function. After downloading the dependencies, Cargo compiles them, to later compile our code.

If we run cargo build , we will get a different output:

$ build charge

That's right, there is no way out! Cargo knows that our project has been built, as well as all its dependencies, so there is no reason to do the whole process again. With nothing to do, just run. If we open src / main.rs again, we make a trivial change, save the changes, we would only see one line:

$ build charge

Compiling riddles v0. 1.0 (file: /// home / you / projects / riddles)

So, we have told Cargo that we wanted any version 0.3.x of rand , and he downloaded the latest version at the time of writing this tutorial, v0.3.8 . But what happens when the next version v0.3.9 is released with a major bugfix? While receiving bugfixes is important, what happens if 0.3.9 contains a regression that breaks our code?

The answer to this problem is the Cargo.lock file, which you will find in your project directory. When you build your project the first time, cargo determines all versions that match your criteria and writes them to the Cargo.lock file . When you build your project in the future, Cargo will notice that a Cargo.lock file exists, and will use the versions specified in it, instead of doing all the work of determining the versions again. This allows you to have an automatically reproducible construction. In other words, we will stay at 0.3.8 until we explicitly upload the version, in the same way the people with whom we have shared our code will do so, thanks to the Cargo.lock file .

But what happens when we want to use v0.3.9 ? Cargo has another command, update , which translates to 'ignore the lock and determine all the latest versions that match what we have specified. If this works, write those versions to the Cargo.lock ' lock file . But, by default, Cargo will only search for versions greater than 0.3.0 and less than 0.4.0 . If we want to move to 0.4.x , we would need to update the Cargo.toml file directly. When we do that, the next time we run cargo build , Cargo will update the index and re-evaluate our rand requirements .

There is much more to say about Cargo and its ecosystem , but for now, that's all we need to know. Cargo makes it really easy to reuse libraries, and Russetters tend to write small projects which are built by a smaller set of packages.

Let us now use rand . Here is our next step:

extern crate rand;

use std :: io;

use rand :: Rng;

fn main () {

println! ("Guess the number!");

let secret_number = rand :: thread_rng (). gen_range (1, 101);

println! ("The secret number is: {}", secret_number);

println! ("Please enter your hunch.");

let mut hunch = String :: new ();

io :: stdin (). read_line (& mut hunch)

.okay()

.expect ("Failed to read line");

println! ("Your hunch was: {}", hunch);

}

The first thing we have done is change the first line. Now it says extern crate rand . Because we declare rand in our [dependencies] section , we can use extern crate to let Rust know that we will be using rand . This is equivalent to a use rand; , so that we can make use of whatever is inside the crate rand through the prefix rand :: .

Then we have added another use line : use rand :: Rng . In a few moments we will be using a method, and this requires that Rng is available for it to work. The basic idea is this: the methods are inside something called 'traits', and for the method to work you need the trait to be available. For more details go to the Traits section .

There are two more lines in the middle:

let secret_number = rand :: thread_rng (). gen_range (1, 101);

println! ("The secret number is: {}", secret_number);

We use the rand :: thread_rng () function to obtain a copy of the random number generator, which is local to the thread of execution in which we are. Because we have made rand :: Rng available through the use rand :: Rng , it has a gen_range () method available. This method accepts two arguments, and generates a random number between them. It is inclusive at the lower limit, but it is exclusive at the upper limit, so we need 1 and 101 to get a number between one and one hundred.

The second line only prints the secret number. This is useful as we develop our program so that we can test it. We will be removing this line for the final version. It's not a game if you print the answer right when you start it!

Try to run the program a few times:

$ charge run

Compiling riddles v0. 1.0 (file: /// home / you / projects / riddles)

Running `target / debug / riddles`

Guess the number!

The secret number is: 7

Please enter your hunch.

4

Your hunch was: 4

$ charge run

Running `target / debug / riddles`

Guess the number!

The secret number is: 83

Please enter your hunch.

5

Your hunch was: 5

Gradious! Next: let's compare our riddle with the secret number.

Comparing riddles

Now that we have user input, let's compare the riddle with our secret number. Here is our next step, although it still doesn't work completely:

extern crate rand;

use std :: io;

use std :: cmp :: Ordering;

use rand :: Rng;

fn main () {

println! ("Guess the number!");

let secret_number = rand :: thread_rng (). gen_range (1, 101);

println! ("The secret number is: {}", secret_number);

println! ("Please enter your hunch.");

let mut hunch = String :: new () ;

io :: stdin (). read_line (& mut hunch)

.okay()

.expect ("Failed to read line");

println! ("Your hunch was: {}", hunch);

match hunch.cmp (& secret_number) {

Ordering :: Less => println! ("Very small!"),

Ordering :: Greater => println! ("Very big!"),

Ordering :: Equal => println! ("You won!"),

}

}

Some pieces here. The first is another use . We have made available a type called std :: cmp :: Ordering . Then, five new bottom lines that use it:

match hunch.cmp (& secret_number) {

Ordering :: Less => println! ("Very small!"),

Ordering :: Greater => println! ("Very big!"),

Ordering :: Equal => println! ("You won!"),

}

The cmp () method can be called to anything that can be compared, it takes a reference to the thing you want to compare with. Returns the Ordering type that we made available previously. We have used a match statement to determine exactly what type of Ordering it is. Ordering is an enum , short for 'enumeration', which looks like this:

enum foo {

Pub,

Baz,

}

With this definition, anything of the Foo type can be either a Foo :: Bar or a Foo :: Baz . We use the :: to indicate the namespace for a particular enum variant .

The Orderin g enum has three possible variants: Less , Equal , and Greater (minor, equal and major respectively). The match statement takes a value of a type, and allows you to create an 'arm' for each possible value. Because we have three possible types of Ordering , we have three arms:

match guess.cmp (& secret_number) {

Ordering :: Less => println! ("Very small!"),

Ordering :: Greater => println! ("Very big!"),

Ordering :: Equal => println! ("You won!"),

}

If it is Less , we print Too small! , if it's Greater , Too big! , and if it's Equal , you've won! . match is really useful, and is often used in Rust.

Earlier I mentioned that it still doesn't work. Let's put it to the test:

$ build charge

Compiling riddles v0. 1.0 (file: /// home / you / projects / riddles)

src / main.rs: 28 : 21 : 28 : 35 error: mismatched types:

expected `& collections :: string :: String`,

found `& _`

(expected struct `collections :: string :: String`,

found integral variable) [E0308]

src / main.rs: 28      match hunch.cmp (& secret_number) {

^ ~~~~~~~~~~~~~

error: aborting due to previous error

Could not compile `riddles`.

Oops! A big mistake. The main thing in it is that we have 'mismatched types'. Rust has a strong, static rate system. However, it also has type inference. When we typed let hunch = String :: new () , Rust was able to infer that hunch must be a String , and so did not make us write the type. With our secret_number , there are a number of types that can have a value between one and one hundred: i32 , a thirty-two-bit number, u32 , an unsigned thirty-two-bit number, or i64 a sixty-four-bit number or others. So far, that hasn't mattered, since Rust by default uses i32 . However, in this case, Rust doesn't know how to compare hunch to secret_number . Both need to be of the same type. In the end, we want to convert the String that we read as input into a real type of number, for comparison purposes. We can do that with three more lines. Here is our new program:

extern crate rand;

use std :: io;

use std :: cmp :: Ordering;

use rand :: Rng;

fn main () {

println! ("Guess the number!");

let secret_number = rand :: thread_rng (). gen_range (1, 101);

println! ("The secret number is: {}", secret_number);

println! ("Please enter your hunch.");

let mut hunch = String :: new ();

io :: stdin (). read_line (& mut hunch)

.okay()

.expect ("Failed to read line");

let hunch: u32 = hunch.trim (). parse ( )

.okay()

.expect ("Please enter a number!");

println! ("Your hunch was: {}", hunch);

match hunch.cmp (& secret_number) {

Ordering :: Less => println! ("Very small!"),

Ordering :: Greater => println! ("Very big!"),

Ordering :: Equal => println! ("You won!"),

}

}

The three new lines:

let hunch: u32 = hunch.trim (). parse ()

.okay()

.expect ("Please enter a number!");

Wait a minute, I thought we already had a hunch ? We do, but Rust allows us to overwrite the previous hunch with a new one. This is frequently used in this same situation, where hunch is a String , but we want to convert it to a u32 . This shadowing allows us to reuse the name hunch instead of forcing us to come up with two unique names like hunch_str and hunch , or others.

We are associating a hunch with an expression that looks like something we wrote earlier:

guess.trim (). parse ()

Followed by an invocation to ok (). Expect () . Here hunch refers to the old version, which was a String that contained our user input in it. The trim () method on String s removes any whitespace at the beginning and end of our character strings. This is important, because we had to press the 'return' key to satisfy read_line () . This means if we type 5 and hit 'return' hunch it looks like this: 5 \ n . The \ n represents 'new line', the enter key. trim () gets rid of this, leaving our string only at 5 . The parse () method on character strings parses a string of characters into some type of number. Because it can parse a variety of numbers, we must give Rust a hint of the exact type of number we want. Hence the hunch part let: u32 . The colon ( : ) after Hunch tell Rust we will note the type. u32 is a thirty-two-bit unsigned integer. Rust has a variety of built-in number types , but we have chosen u32 . It is a good default option for a small positive number.

Like read_line () , our call to parse () could cause an error. What if our character string contains A ߑ ? There would be no way to convert that to a number. This is why we will do the same as we did with read_line () : use the ok () and expect () methods to end abruptly if there are any errors.

Let's try our program!

$ charge run

Compiling riddles v0. 1.0 (file: /// home / you / projects / riddles)

Running `target / riddles`

Guess the number!

The secret number is: 58

Please enter your hunch.

76

Your hunch was: 76

Very big!

Excellent! You can see that I have even added spaces before my attempt, and still the program determined that I try 76. Run the program a few times, and verify that guessing the number works, as well as trying a very small number.

Now we have most of the game working, but we can only try to guess once. Let's try to change that by adding cycles!

Iteration

The loop keyword provides an infinite loop. Let's add it:

Guess the number! The secret number is: 58 Please enter your riddle. 76 Your hunch was: 76 Very big!

extern crate rand;

use std :: io;

use std :: cmp :: Ordering;

use rand :: Rng;

fn main () {

println! ("Guess the number!");

let secret_number = rand :: thread_rng (). gen_range (1, 101);

println! ("The secret number is: {}", secret_number);

loop {

println! ("Please enter your hunch.");

let mut hunch = String :: new ();

io :: stdin (). read_line (& mut hunch)

.okay()

.expect ("Failed to read line");

let hunch: u32 = hunch.trim (). parse ()

.okay()

.expect ("Please enter a number!") ;

println! ("Hunch: {}", hunch);

match hunch.cmp (& secret_number) {

Ordering :: Less => println! ("Very small!"),

Ordering :: Greater => println! ("Very big!"),

Ordering :: Equal => println! ("You won!"),

}

}

}

Test it. But wait, didn't we just add an infinite loop? Yep. Remember our discussion about parse () ? If we give a non - numerical answer, we will return ( return ) and will finish execution. Observe:

$ charge run

Compiling riddles v0. 1.0 (file: /// home / you / projects / riddles)

Running `target / riddles`

Guess the number!

The secret number is: 59

Please enter your hunch.

Four. Five

Your hunch was: 45

Very small!

Please enter your hunch.

60

Your hunch was: 60

Very big!

Please enter your hunch.

59

Your hunch was: 59

You won!

Please enter your hunch.

quit

thread '<main>' panicked at 'Please type a number!'

Ha! quit in effect ends the execution. As well as any other entry that is not a number. Well this is suboptimal to say the least. Let's get out first when we win:

extern crate rand;

use std :: io;

use std :: cmp :: Ordering;

use rand :: Rng;

fn main () {

println! ("Guess the number!");

let secret_number = rand :: thread_rng (). gen_range (1, 101);

println! ("The secret number is: {}", secret_number);

loop {

println! ("Please enter your hunch.");

let mut hunch = String :: new ();

io :: stdin (). read_line (& mut hunch)

.okay()

.expect ("Failed to read line");

let hunch: u32 = hunch.trim (). parse ()

.okay()

.expect ("Please enter a number!");

println! ("Your hunch was: {}", hunch) ;

match hunch.cmp (& secret_number) {

Ordering :: Less => println! ("Very small!"),

Ordering :: Greater => println! ("Very big!"),

Ordering :: Equal => {

println! ("You won!");

break;

}

}

}

}

By adding the break line after "You won!", We will break the cycle when we win. Exiting the loop also means exiting the program, since it is the last thing in main () . We only have one improvement left to make: when someone enters a non-numeric value, we don't want to end the run, we just want to ignore it. We can do it in the following way:

extern crate rand;

use std :: io;

use std :: cmp :: Ordering;

use rand :: Rng;

fn main () {

println! ("Guess the number!");

let secret_number = rand :: thread_rng (). gen_range (1, 101);

println! ("The secret number is: {}", secret_number);

loop {

println! ("Please enter your hunch.");

let mut hunch = String :: new ();

io :: stdin (). read_line (& mut hunch)

.okay()

.expect ("Failed to read line");

let hunch: u32 = match hunch.trim (). parse () {

Ok (num) => num,

Err (_) => continue,

};

println! ("Your hunch was: {}", hunch);

match hunch.cmp (& secret_number) {

Ordering :: Less => println! ("Very small!"),

Ordering :: Greater => println! ("Very big!"),

Ordering :: Equal => {

println! ("You won!");

break;

}

}

}

}

These are the lines that have changed:

let hunch: u32 = match hunch.trim (). parse () {

Ok (num) => num,

Err (_) => continue,

};

This is how we went from 'abruptly ending an error' to 'effectively handling the error', through changing ok (). Expect () to a match statement . The Result returned by parse () is an enum just like Ordering , but in this case each variant has associated data: Ok is successful, and Err is a failure. Each contains more information: the parsed integer in the successful case , or a type of error. In this case we match on Ok (num) , which assigns the internal value of the Ok to name num , and then returns on the right side. In the case of Err , we don't care what kind of error it is, that's why we use _ instead of a name. This ignores the error and continue moves us to the next iteration of the loop .

Now we should be fine! Let's try:

$ charge run

Compiling riddles v0. 1.0 (file: /// home / you / projects / riddles)

Running `target / riddles`

Guess the number!

The secret number is: 61

Please enter your hunch.

10

Your hunch was: 10

Very small!

Please enter your hunch.

99

Your hunch was: 99

Very small!

Please enter your hunch.

foo

Please enter your hunch.

61

Your hunch was: 61

You won!

Great! With one last upgrade, we finish the puzzle game. Can you imagine what it is? It is correct, we do not want to print the secret number. It was good for testing, but it ruins our game. Here is our final source code:

extern crate rand;

use std :: io;

use std :: cmp :: Ordering;

use rand :: Rng;

fn main () {

println! ("Guess the number!");

let secret_number = rand :: thread_rng (). gen_range (1, 101);

loop {

println! ("Please enter your hunch.");

let mut hunch = String :: new ();

io :: stdin (). read_line (& mut hunch)

.okay()

.expect ("Failed to read line");

let hunch: u32 = match hunch.trim (). parse () {

Ok (num) => num,

Err (_) => continue,

} ;

println! ("Your hunch was: {}", hunch);

match hunch.cmp (& secret_number) {

Ordering :: Less => println! ("Very small!"),

Ordering :: Greater => println! ("Very big!"),

Ordering :: Equal => {

println! ("You won!");

break;

}

}

}

}

Completed!

At this point, you have successfully finished the guessing game! Congratulations!

This first project taught you a lot: let , match , methods, associated functions, using external crates, and more. Our next project will demonstrate even more.

The Dinner of the Philosophers

For our second project, let's take a look at a classic concurrency problem. It's called 'The Dinner of the Philosophers'. It was originally conceived by Dijkstra in 1965, but we will use a slightly adapted version of this paper by Tony Hoare in 1985.

In ancient times, a wealthy philanthropist set up a university to house five eminent philosophers. Each philosopher had a room in which he could carry out his professional activity of thought: there was also a common dining room, furnished with a circular table, surrounded by five chairs, each identified with the name of the philosopher who sat in it. Philosophers sat counterclockwise around the table. To the left of each philosopher lay a golden fork, and in the center a bowl of spaghetti, which was constantly refilled. A philosopher was expected to spend most of his time thinking; but when they were hungry, go to the dining room and take the fork to your left and dip it in the spaghetti. But such was the tangled nature of spaghetti that a second fork was required to bring it to the mouth. The philosopher therefore also had to take the fork to his right. When they finished they had to lower both forks, get up from the chair and continue thinking. Of course, a fork can be used by only one philosopher at a time. If another philosopher wants it, he has to wait until the fork is available again.

This classic problem exhibits some elements of concurrency. The reason for this is that it is an effectively difficult solution to implement: a simple implementation can generate a deadlock. For example, consider a simple algorithm that could solve this problem:

                   A philosopher takes the fork to his left.

                   Then take the fork to your right.

                   Eat.

                   Lower the forks.

Now, let's imagine this sequence of events:

                   Philosopher 1 begins the algorithm, taking the fork to his left.

                   Philosopher 2 begins the algorithm, taking the fork to his left.

                   Philosopher 3 begins the algorithm, taking the fork to his left.

                   Philosopher 4 begins the algorithm, taking the fork to his left.

                   Philosopher 5 begins the algorithm, taking the fork to his left.

                   ...? All forks have been taken, but no one can eat!

There are different ways to solve this problem. We will guide you through the solution of this tutorial. For now, let's start by modeling the problem. Let's start with the philosophers:

struct Philosopher {

name: String ,

}

impl Filosofo {

fn new (name: & str ) -> Philosopher {

Philosopher {

name: name.to_string (),

}

}

}

fn main () {

let f1 = Philosopher :: new ( "Judith Butler" );

let f2 = Philosopher :: new ( "Gilles Deleuze" );

let f3 = Philosopher :: new ( "Karl Marx" );

let f4 = Philosopher :: new ( "Emma Goldman" );

let f5 = Philosopher :: new ( "Michel Foucault" );

}

Here, we create a structure (struct) to represent a philosopher. For now the name is all we need. We choose the String type for the name, instead of & str . Generally speaking, working with the type that owns (owns) your data is easier than working with one that uses references.

Let's continue:

# struct Philosopher {

# name: String ,

#}

impl Filosofo {

fn new (name: & str ) -> Philosopher {

Philosopher {

name: name.to_string (),

}

}

}

This impl block allows us to define things in Philosopher structures . In this case we are defining an 'associated function' called new . The first line looks like this:

# struct Philosopher {

# name: String ,

#}

# impl Philosopher {

fn new (name: & str ) -> Philosopher {

# Philosopher {

# name: name.to_string (),

#}

#}

#}

We get an argument, name , of type & str . A reference to another character string. This returns an instance of our Philosopher structure .

# struct Philosopher {

# name: String ,

#}

# impl Philosopher {

#    fn new (name: & str ) -> Philosopher {

Philosopher {

name: name.to_string (),

}

#}

#}

The above creates a new Philosopher , and assigns our name argument to the name field . Not the argument itself, because we call .to_string () on it. Which creates a copy of the string pointed to our & str , and gives us a new String , which is the type of the field name of Filosofo .

Why not accept a String directly? It is easier to call. If we received a String but the caller had a & str they would be forced to call .to_string () on their side. The downside to this flexibility is that we always make a copy. For this little program, this is not particularly important, and we know that we will be using short strings anyway.

One last thing you may have noticed: we only define a Philosopher , and we don't seem to do anything with it. Rust is an 'expression-based' language, which means that almost anything in Rust is an expression that returns a value. This is true for functions too, the last expression is automatically returned. Because we create a new Philosopher as the last expression of this function, we end up returning it.

The name new () is nothing special to Rust, but it is a convention for functions that create new instances of structures. Before we talk about why, we take a look at main () again:

# struct Philosopher {

# name: String ,

#}

#

# impl Philosopher {

#     fn new (name: & str ) -> Philosopher {

# Philosopher {

# name: name.to_string (),

#}

#}

#}

#

fn main () {

let f1 = Philosopher :: new ( "Judith Butler" );

let f2 = Philosopher :: new ( "Gilles Deleuze" );

let f3 = Philosopher :: new ( "Karl Marx" );

let f4 = Philosopher :: new ( "Emma Goldman" );

let f5 = Philosopher :: new ( "Michel Foucault" );

}

Here, we create five variables with five new philosophers. These are my five favorites, but you can replace them with whomever you prefer. If you had not defined the new () function , main () would look like this:

# struct Philosopher {

# name: String ,

#}

fn main () {

let f1 = Philosopher {name: "Judith Butler" .to_string ()};

let f2 = Philosopher {name: "Gilles Deleuze" .to_string ()};

let f3 = Philosopher {name: "Karl Marx" .to_string ()};

let f4 = Philosopher {name: "Emma Goldman" .to_string ()};

let f5 = Philosopher {name: "Michel Foucault" .to_string ()};

}

A little louder. Using new also has other advantages, but even in this simple case it ends up being of better use.

Now that we have the basics in place, there are a number of ways in which we can attack the larger problem. I like to start at the end: let's create a way for each philosopher to finish eating. As a small step, let's make a method, and then iterate through all the philosophers calling it:

struct Philosopher {

name: String ,

}

impl Filosofo {

fn new (name: & str ) -> Philosopher {

Philosopher {

name: name.to_string (),

}

}

fn eat (& self ) {

println! ( "{} has finished eating." , self .name);

}

}

fn main () {

let philosophers = vec! [

Philosopher :: new ( "Judith Butler" ),

Philosopher :: new ( "Gilles Deleuze" ),

Philosopher :: new ( "Karl Marx" ),

Philosopher :: new ( "Emma Goldman" ),

Philosopher :: new ( "Michel Foucault" ),

];

for f in & philosophers {

f.comer ();

}

}

Let's look at main () first . Instead of having five individual variables for our philosophers, we create a Vec <T> . Vec <T> is also called a 'vector', and is an array capable of growth. Then we use a [ for ] [for] loop to iterate through the vector, getting a reference to each philosopher at once.

In the body of the loop, we call f.comer (); , which is defined as:

fn eat (& self) {

println! ("{} has finished eating.", self.name);

}

In Rust, methods receive an explicit parameter self . This is why eating () is a method and new is an associated function: new () has no self . For our first version of eating () , we only printed the name of the philosopher, and we mention that he has finished eating. Running this program should generate the following output:

Judith Butler has finished eating.

Gilles Deleuze has finished eating.

Karl Marx has finished eating.

Emma Goldman has finished eating.

Michel Foucault has finished eating.

Very easy, everyone has finished eating! But we haven't implemented the real problem yet, so we're not done yet!

Next, we not only want to just finish eating, but actually eat. Here is the next version:

use std :: thread;

struct Philosopher {

name: String ,

}

impl Filosofo {

fn new (name: & str ) -> Philosopher {

Philosopher {

name: name.to_string (),

}

}

fn eat (& self ) {

println! ( "{} is eating." , self .name);

thread :: sleep_ms ( 1000 );

println! ( "{} has finished eating." , self .name);

}

}

fn main () {

let philosophers = vec! [

Philosopher :: new ( "Judith Butler" ),

Philosopher :: new ( "Gilles Deleuze" ),

Philosopher :: new ( "Karl Marx" ),

Philosopher :: new ( "Emma Goldman" ),

Philosopher :: new ( "Michel Foucault" ),

];

for f in & philosophers {

f.comer ();

}

}

Only a few changes. Let us analyze them part by part.

use std :: thread;

use makes names available in our scope. We will start using the thread module of the standard library, and that is why we need to use it .

fn eat (& self) {

println! ("{} is eating.", self.name);

thread :: sleep_ms (1000) ;

println! ("{} has finished eating.", self.name);

}

We are now printing two messages, with a sleep_ms () in the middle. Which simulates the time it takes for a philosopher to eat.

If you run this program, you should see each philosopher eat at once:

Judith Butler is eating.

Judith Butler has finished eating.

Gilles Deleuze is eating.

Gilles Deleuze has finished eating.

Karl Marx is eating.

Karl Marx has finished eating.

Emma Goldman is eating.

Emma Goldman has finished eating.

Michel Foucault is eating.

Michel Foucault has finished eating.

Excellent! We're moving forward. There is only one detail: we are not operating concurrently, which is central to our problem!

To make our philosophers eat concurrently, we need to make a small change.

Here is the next iteration:

use std :: thread;

struct Philosopher {

name: String ,

}

impl Filosofo {

fn new (name: & str ) -> Philosopher {

Philosopher {

name: name.to_string (),

}

}

fn eat (& self ) {

println! ( "{} is eating." , self .name);

thread :: sleep_ms ( 1000 );

println! ( "{} has finished eating." , self .name);

}

}

fn main () {

let philosophers = vec! [

Philosopher :: new ( "Judith Butler" ),

Philosopher :: new ( "Gilles Deleuze" ),

Philosopher :: new ( "Karl Marx" ),

Philosopher :: new ( "Emma Goldman" ),

Philosopher :: new ( "Michel Foucault" ),

];

let handles: Vec <_> = philosophers.into_iter (). map (| f | {

thread :: spawn (move || {

f.comer ();

})

}). collect ();

for h in handles {

h.join (). unwrap ();

}

}

All we have done is change the loop in main () , and added a second! This is the first change:

let handles: Vec <_> = philosophers.into_iter (). map (| f | {

thread :: spawn (move || {

f.comer ();

})

}). collect ();

Even so they are only five lines, they are five dense lines. Let's analyze by parts.

let handles: Vec <_> =

We introduce a new variable, called handles . We have given this name because we will create some new threads, which will result in some handles (handles, handles) to these threads which will allow us to control their operation. We need to explicitly annotate the type, due to something we'll reference later. The _ is a placeholder for a type. We're saying " handles is a vector of something, but you, Rust, can determine what that something is."

philosophers.into_iter (). map (| f | {

We take our list of philosophers and call into_iter () on it. This creates an iterator that takes over (belongs to) each philosopher. We need to do this in order to pass the philosophers to our threads. Then we take that iterator and call map on it, a method that takes a closure as an argument and calls that closure on each of the elements at once.

thread :: spawn (move || {

f.comer ();

})

This is where concurrency occurs. The thread :: spawn function takes a closure as an argument and executes that closure on a new thread. The closure needs an extra annotation, move , to indicate that the closure is going to take over the values ​​it is capturing. Mainly, the variable f of the map function .

Inside the thread, all we do is call eat (); in f .

}). collect ();

Finally, we take the result of all those calls to map and collect them. collect () will convert them into a collection of some type, which is why we note the return type: we want a Vec <T> . The elements are the returned values ​​of the calls to thread :: spawn , which are handles to those threads. Whew!

for h in handles {

h.join (). unwrap ();

}

At the end of main () , we iterate through the handles by calling join () on them, which blocks execution until the thread has completed execution. This ensures that the thread completes its execution before the program ends.

If you run this program, you will see that philosophers eat without order! We have multi-threads!

Gilles Deleuze is eating.

Gilles Deleuze has finished eating.

Emma Goldman is eating.

Emma Goldman has finished eating.

Michel Foucault is eating.

Judith Butler is eating.

Judith Butler has finished eating.

Karl Marx is eating.

Karl Marx has finished eating.

Michel Foucault has finished eating.

But that about the forks we have not fully modeled them yet.

To do so, let's create a new struct :

use std :: sync :: Mutex;

struct Table {

forks: Vec <Mutex <() >>,

}

This table contains a Mutex vector . A mutex is a way to control concurrency, only one thread can access content at a time. This is exactly the property we need for our holders. We use an empty pair, () , inside the mutex, because we are not going to use the value, we will just hold on to it.

Let's modify the program to use Mesa :

use std :: thread;

use std :: sync :: {Mutex, Arc};

struct Philosopher {

name: String ,

left: usize,

right: usize,

}

impl Filosofo {

fn new (name: & str , left: usize, right: usize) -> Philosopher {

Philosopher {

name: name.to_string (),

left: left,

right: right,

}

}

fn eat (& self , table: & table) {

let _left = table.forks [ self. left] .lock (). unwrap () ;

let _right = table.forks [ self. right] .lock (). unwrap ();

println! ( "{} is eating." , self .name);

thread :: sleep_ms ( 1000 );

println! ( "{} has finished eating." , self .name);

}

}

struct Table {

forks: Vec <Mutex <() >>,

}

fn main () {

let table = Arc :: new (Table {forks: vec! [

Mutex :: new (()),

Mutex :: new (()),

Mutex :: new (()),

Mutex :: new (()),

Mutex :: new (()),

]});

let philosophers = vec! [

Philosopher :: new ( "Judith Butler" , 0 , 1 ),

Philosopher :: new ( "Gilles Deleuze" , 1 , 2 ),

Philosopher :: new ( "Karl Marx" , 2 , 3 ),

Philosopher :: new ( "Emma Goldman" , 3 , 4 ),

Philosopher :: new ( "Michel Foucault" , 0 , 4 ),

];

let handles: Vec <_> = philosophers.into_iter (). map (| f | {

let table = table.clone ();

thread :: spawn (move || {

f.comer (& table);

} )

}). collect ();

for h in handles {

h.join (). unwrap ();

}

}

Many changes! However, with this iteration, we have obtained a functional program. Let's see the details:

use std :: sync :: {Mutex, Arc};

We will use another std :: sync package structure : Arc <T> .

We will talk more about it when we use it.

struct Philosopher {

name: String,

left: usize,

right: usize,

}

We are going to need to add two more fields to our Philosopher structure . Each philosopher will have two forks: the one on the left and the one on the right. We will use the type usize to indicate them, because this is the type with which the vectors are indexed. These two values ​​will be indexes in the holders that our Table owns.

fn new (name: & str, left: usize, right: usize) -> Philosopher {

Philosopher {

name: name.to_string (),

left: left,

right: right,

}

}

Now we need to build those values left and right , so that we can add them to new () .

fn eat (& self, table: & table) {

let _left = table.forks [self.left] .lock (). unwrap ();

let _right = table.forks [self.right] .lock (). unwrap ();

println! ("{} is eating.", self.name);

thread :: sleep_ms (1000);

println! ("{} has finished eating.", self.name);

}

We have two new lines, we have also added an argument, table . Access the list of holders of the table , and then use self.izquierda and self.derecha to access the fork in a particular index. That gives us access to the Mutex at that index, where we call lock () . If the mutex is currently being accessed by someone else, we will block it until it is available.

The call to lock () may fail, and if it does, we want to end it abruptly. In this case the error that can occur is that the mutex this 'poisoning' ( 'poisoned'), which is what happens when the thread does panic while holding the lock. Because this shouldn't happen, we simply use unwrap () .

Another strange thing about these lines: we have named the results _ left and _ right . What about that sub-script? Well, we don't actually plan to use the value inside the lock. We just want to acquire it. As a consequence, Rust will warn us that we never use value. Through the use of the sub-script we tell Rust what we wanted, that way he would not generate the warning.

What about releasing the lock ?, Well, this will happen when _left and _right go out of scope, automatically.

let table = Arc :: new (Table {forks: vec! [

Mutex :: new (()),

Mutex :: new (()),

Mutex :: new (()),

Mutex :: new (()),

Mutex :: new (()),

]});

Then in main () , we create a new Table and wrap it in an Arc <T> . 'arc' comes from 'atomic reference count', we need to share our Table between multiple threads. As we share it, the reference count will go up, and when each thread ends, it will go down.

let philosophers = vec! [

Philosopher :: new ("Judith Butler", 0, 1),

Philosopher :: new ("Gilles Deleuze", 1, 2),

Philosopher :: new ("Karl Marx", 2, 3),

Philosopher :: new ("Emma Goldman", 3, 4),

Philosopher :: new ("Michel Foucault", 0, 4),

];

We need to pass our values left and right to the constructors of our Philosophers . But there is one more detail here, and it is very important. If you look at the pattern, it is consistent to the end, Monsieur Foucault must have 4, 0 as arguments, but instead it has 0, 4 . This is what prevents deadlocks, indeed: one of the philosophers is left-handed! That is one way to solve the problem, and in my opinion, it is the simplest.

let handles: Vec <_> = philosophers.into_iter (). map (| f | {

let table = table.clone () ;

thread :: spawn (move || {

f.comer (& table);

})

}). collect ();

Finally, inside our map () / collect () loop , we call mesa.clone () . The clone () method on Arc <T> is what increments the reference count, and when it goes out of scope, decrements it. You will notice that we can introduce a new table variable , and this will overwrite the old one. This is often used so that you don't have to make up two unique names.

With all this, our program works! Only two philosophers can eat at any given time and consequently you will have an exit that will look like this:

Gilles Deleuze is eating.

Emma Goldman is eating.

Emma Goldman has finished eating.

Gilles Deleuze has finished eating.

Judith Butler is eating.

Karl Marx is eating.

Judith Butler has finished eating.

Michel Foucault is eating.

Karl Marx has finished eating.

Michel Foucault has finished eating.

Congratulations! You have implemented a classic concurrency problem in Rust.

Rust Within Other Languages

For our third project, we will choose something that demonstrates one of Rust's greatest strengths: the absence of an execution environment.

As organizations grow, they progressively make use of a multitude of programming languages. Different programming languages ​​have different strengths and weaknesses, and a polyglot architecture allows a particular language to be used where its strengths make sense and another language is weak.

A very common area where many programming languages ​​are weak is performance at runtime. Often, using language that is slow, but offers increased productivity for the programmer, is a worthwhile balance. To help mitigate this, these languages ​​provide a way to write a part of your system in C and then call that code as if it had been written in a higher-level language. This facility is called the 'foreign function interface', commonly shortened to 'FFI'.

Rust has support for FFI in both directions: it can easily call C code, but crucially it can be called as easily as C. Combined with the absence of a garbage collector and low runtime requirements, Rust is a candidate for be embedded within other languages ​​when you need those extra ooKmh.

The problem

There are many problems that we could have chosen, but we will choose an example in which Rust has a clear advantage over other languages: numerical computing and threads.

Many languages, in honor of consistency, place numbers on the mound, rather than on the pile. Especially in languages ​​focused on object-oriented programming and the use of a garbage collector, memory allocation from the mound is the default behavior. Sometimes optimizations can put certain numbers on the stack, but instead of relying on an optimizer to do this job, we might want to make sure that we are always using primitive numbers instead of some kind of object.

Second, many languages ​​have a 'global interpreter lock' (GIL), which limits concurrency in many situations. This is done in the name of security, which is a positive effect, but limits the amount of work that can be done concurrently, which is a huge negative.

To emphasize these 2 aspects, we will create a small project that uses these two aspects to a great extent. Because the focus of the example is to embed Rust in other languages, instead of the problem itself, we will use a toy example:

Start ten threads. Within each thread, it counts from one to five million. After all threads have finished, print "completed!".

I have chosen five million based on my particular computer. Here's an example of this Ruby code:

threads = []

10 .times do

threads << Thread .new do

count = 0

5_000_000 .times do

count    = 1

end

end

end

threads.each {| t | t.join}

puts "completed!"

Try running this example, and pick a number that runs for a few seconds. Depending on the hardware of your computer, you will have to increase or decrease the number.

On my system, running this program takes 2,156 . If I use some kind of process monitoring tool, like top , I can see that it only uses one kernel on my machine. The GIL present doing its job.

While it is true that you are in a synthetic program, one could imagine many problems similar to this in the real world. For our purposes, picking up a few threads and occupying them represents a kind of parallel and expensive computing.

A Rust library

Let's write this problem in Rust. First, let's create a new project with Cargo:

$ charge new embed

$ cd embed

This program is easy to write in Rust:

use std :: thread;

fn process () {

let handles: Vec <_> = ( 0 .. 10 ) .map (| _ | {

thread :: spawn (|| {

let mut _x = 0 ;

for _ in ( 0 .. 5_000_000 ) {

_x    = 1

}

})

}). collect () ;

for h in handles {

h.join (). ok (). expect ( "A thread could not be joined!" );

}

}

Some of this should look familiar to previous examples. We start ten threads, collecting them in a vector handles . Within each thread, we iterate five million times, adding one to _x on each iteration. Why the sub-script? Well, if we remove it and then compile:

$ build charge

Compiling embed v0. 1.0 (file: /// Users / goyox86 / Code / rust / embed)

src / lib.rs: 3 : 1 : 16 : 2 warning: function is never used: ` process` , # [warn (dead_code)] on by default

src / lib.rs: 3 fn process () {

src / lib.rs: 4 let handles: Vec <_> = ( 0 .. 10 ) .map (| _ | {    

src / lib.rs: 5          thread :: spawn (|| {

src / lib.rs: 6 let mut x = 0 ;            

src / lib.rs: 7 for _ in ( 0 .. 5 _000_000) {            

src / lib.rs: 8                  x    = 1

...

src / lib.rs: 6 : 17 : 6 : 22 warning: variable `x` is assigned to, but never used, # [warn (unused_variables)] on by default

src / lib.rs: 6 let mut x = 0 ;            

^ ~~~~

The first warning is due is due to building a library. If we had a test for this function, the warning would disappear. But for now it is never called.

The second is related to x versus _x . As a product of actually doing nothing with x we get a warning. That, in our case, is perfectly fine, since we want to waste CPU cycles. Using a prefix sub-hyphen we remove the warning.

Finally, we join each of the threads.

So far, however, it is a Rust library, and it doesn't expose anything that can be called from C. If we wanted to connect it to another language, in its current state, it wouldn't work. We just need to make a few small changes to fix it. The first thing is to modify the principle of our code:

# [no_mangle]

pub extern fn process () {

We must add a new attribute, no_mangle . When we create a Rust library, it changes the name of the function in the compiled output. The reasons for this are beyond the scope of this tutorial, but in order for other languages ​​to know how to call the function, we must prevent the compiler from changing the name in the compiled output. This attribute disables that behavior.

The other change is the pub extern . The pub means that this function can be called from outside this module, and the extern says that it can be called from C. That's it! Not many changes.

The second thing we need to do is change a setting in our Cargo.toml . Add this at the end:

[lib]

name = "embed"

crate-type = [ "dylib" ]

These lines inform Rust that we want to compile our library into a standard dynamic library. Rust compiles an 'rlib', a specific format from Rust.

Now let's build the project:

$ charge build --release

Compiling embed v0. 1.0 (file: /// Users / goyox86 / Code / rust / embed)

We have chosen charge build --release , which builds the project with optimizations. We want it to be as fast as possible! You can find the library output in target / release :

$ ls target / release /

build deps examples libembeber.dylib native

That libembeber.dylib is our 'shared objects' library. We can use this library like any shared object library written in C! As a note, this could be libembeber.so or libembeber.dll , depending on the platform.

Now that we have our Rust library, let's use it from Ruby.

Ruby

Create an embeber.rb file inside our project, and put this inside:

require 'ffi'

Hello module

extend FFI :: Library

ffi_lib 'target / release / libembeber.dylib'

attach_function : process , [] ,: void

end

Hello .process

puts 'completed!'

Before we can run it, we need to install the ffi gem :

$ gem install ffi # this may need sudo

Fetching: ffi- 1.9 . 8 .gem ( 100 %)

Building native extensions. This could take a while ...

Successfully installed ffi- 1.9 . 8

Parsing documentation for ffi- 1.9 . 8

Installing ri documentation for ffi- 1.9 . 8

Done installing documentation for ffi after 0 seconds

1 gem installed

Finally, let's try running it:

$ ruby ​​embeber.rb

completed!

$

Whoa, that was fast! On my system, it takes 0.086 seconds, as opposed to two seconds than the pure Ruby version. Let's analyze this Ruby code:

require 'ffi'

First we need to require the ffi gem . It allows us to interact with a Rust library like a C library.

Hello module

extend FFI :: Library

ffi_lib 'target / release / libembeber.dylib'

The Hello module is used to attach the native functions of the shared library. Inside, we extend the FFI :: Library module and then call the ffi_lib method to load our shared object library. We simply pass the path in which our library is stored, which, as we saw earlier, is target / release / libembeber.dylib .

attach_function : process , [] ,: void

The attach_function method is provided by the FFI gem. It is what connects our process () function in Rust to a method in Ruby with the same name. Because process () receives no arguments, the second parameter is an empty array, and since it returns nothing, we pass : void as the final argument.

Hello .process

This is the call to Rust. The combination of our module and the call to attach_function have configured everything. It looks like a Ruby method but it is actually Rust code!

puts 'completed!'

Finally, and as a requirement of our project, we print completed! .

That's it! As we have seen, bridging the two languages ​​is really easy, and it buys us a lot of performance.

Next, let's try Python!

Python

Create an embeber.py file in this directory, and put this in:

from ctypes import cdll

lib = cdll.LoadLibrary ( "target / release / libembeber.dylib" )

lib.process ()

print ( "completed!" )

Even easier! We use cdll from the ctypes module . A quick call to LoadLibrary later, and then we can call process () .

On my system, it takes 0.017 seconds. Quickly!

Node.js

Node is not a language, but it is currently the dominant server-side Javascript implementation.

To make FFI with Node, we first need to install the library:

$ npm install ffi

After it is installed, we can use it:

var ffi = require ( 'ffi' );

var lib = ffi.Library ( 'target / release / libembeber' , {

'process' : [ 'void' , []]

});

lib.process ();

console .log ( "completed!" );

It looks more like the Ruby example than the Python example. We use the ffi module to access ffi.Library () , which allows us to load our shared object library. We need to note the return type and the types of the function's arguments, which are void for the return and an empty array to represent no arguments. From there we simply call the process () function and print the result.

On my system, this example takes a quick 0.092 seconds.

Chapter III

Rust Cash

So, you've learned how to write some Rust code. But there is a difference between writing any Rust code and writing good Rust code.

This section consists of relatively independent tutorials that show you how to take your Rust to the next level. Common patterns and characteristics of the standard library will be presented. You can read these sections in the order you prefer.

The Stack and the Mound

Like a systems language, Rust operates at a low level. If you come from a high-level language, there are some aspects of system programming languages ​​that you may not be familiar with. The most important is the functioning of the memory, with the battery and the mound. If you are familiar with how languages ​​like C use mapping from the stack, this chapter will be a review. If you are not, you will learn about this general concept, but with a Rustero approach.

Memory management

These two terms refer to memory management. The stack and the mound are abstractions that help determine when to allocate and free memory.

Here's a high-level comparison:

The stack is very fast, and that is where the memory is allocated by default in Rust. But the assignment is local to a function call, and it is limited in size. The mound on the other hand, is slower, and is assigned by your program. But it is effectively unlimited in size, and globally accessible.

The battery

Let's talk about this Rust program:

fn main () {

let x = 42 ;

}

This program has a variable (variable binding), x . Memory has to be allocated from somewhere. Rust allocates from the default stack, which means that the basic values ​​'go on the stack'. But what does this mean?

Let's see, when a function is called, some memory is allocated for its local variables and other extra information. This memory is called 'activation register' ('stack frame'), for the purpose of this tutorial, we will ignore the extra information and only consider the local variables to which we are allocating memory. So in this case, when main () is executed, we assign a 32-bit integer to our activation register. All of this is handled automatically, as you can see, we didn't have to write any special Rust code or anything else.

When the function ends, your activation record is released or deallocated. This happens automatically, we didn't have to do anything special here.

That's it for this simple program. The key thing to understand here is that memory allocation from the stack is very, very fast. Because we know all the local variables in advance, we can get all the memory at once. And because we'll throw it all away, we can get rid of it real quick, too.

The disadvantage is that we cannot keep values ​​hanging around if we need them for a period longer than the lifetime of a function. We also haven't talked about what that name means, 'stack'. To do this we need a slightly more complex example:

fn foo () {

let y = 5 ;

let z = 100 ;

}

fn main () {

let x = 42 ;

foo ();

}

This program has three variables in total: two in foo () , one in main () . Just like before, when main () is called, a single integer is assigned for its activation record. But before we demonstrate what happens when foo () is called, we need to visualize what is happening in memory. Your operating system presents your program with a very simple vision: an immense list of addresses, from 0 to a very large number, which represents how much RAM the machine has. For example if you have a gigabyte of RAM, your addresses will go from 0 to 1,073,741,824 , a number that comes from 2 30 , the number of bytes in a gigabyte.

This memory is a kind of giant arrangement: addresses start at zero and increase to the final number. So here's a diagram of our first activation record:

Address

Name

Value

0

x

42

We have placed x in address 0 , with the value 42

When foo () is called a new activation record is assigned:

Address

Name

Value

two

z

100

one

and

5

0

x

42

Since 0 was reserved for the first frame, 1 and 2 are used for the activation record of foo () . The stack grows upwards, as we call more functions.

There are some important things to note here. The numbers 0, 1, and 2 exist for illustrative purposes only, and have no relation to the numbers that a computer would actually use. In particular, the series of addresses are separated by a number of bytes, and that separation may even exceed the size of the value being stored.

After foo () ends, your activation record is released:

Address

Name

Value

0

x

42

And then after main () ends, this last value goes away. Easy!

It is called a stack because it works like a stack of plates: the first plate you place is the last plate you will remove. Stacks are sometimes called 'last in, first out queues' queues, for these reasons the last value you put in the stack will be the first you will get from it.

Let's try an example of three levels:

fn bar () {

let i = 6 ;

}

fn foo () {

let a = 5 ;

let b = 100 ;

let c = 1 ;

Pub();

}

fn main () {

let x = 42 ;

foo ();

}

Well, in the first instance, we call main () :

Address

Name

Value

0

x

42

Then main () calls foo () :

Address

Name

Value

3

c

one

two

b

100

one

to

5

0

x

42

Then foo () calls bar () :

Address

Name

Value

4

i

6

3

c

one

two

b

100

one

to

5

0

x

42

Uff! Our stack is growing.

After bar () ends, its activation record is released, leaving only foo () and main () :

Address

Name

Value

3

c

one

two

b

100

one

to

5

0

x

42

Then foo () ends, leaving only main ()

Address

Name

Value

0

x

42

We are done then. It is understood? It is like stacking plates: you add to the top and remove from it.

The Mound

Now, all of this works fine, but not everything works that way. Sometimes, you need to pass memory between different functions, or keep memory alive for a longer time than executing a function. For this we use the mound.

In Rust, you can allocate memory from the mound with the type Box <T> .

Here is an example:

fn main () {

let x = Box :: new ( 5 );

let y = 42 ;

}

Here, what happens when main () is called:

Address

Name

Value

one

and

42

0

x

??????

We allocate space for two variables on the stack. y is 42 , as we know so far, but what about x ? Well, x is a Box <i32> , and the boxes allocate memory from the mound. The value of the box in question is a structure that has a pointer to 'the mound'. When function execution begins, and Box :: new () is called, it allocates some memory for the mound and places 5 there. Memory now looks like this:

Address

Name

Value

2 30

5

...

...

...

one

and

42

0

x

2 30

We have 2 30 on our hypothetical computer with 1GB of RAM. And because our stack grows from scratch, the easiest way to allocate memory is from the other end. So our first value is in the highest place in memory. And the value of the structure in x has a flat pointer (raw pointer) to the place we have assigned on the mound, then the value of x EN 2 30 , the memory address we have requested.

We haven't talked much about what allocating and releasing memory actually means in these contexts. Going into the deep detail of it is beyond the scope of this tutorial, the important thing to note is that the mound is not a simple pile growing from the opposite side. We will have an example of this later in the book, but because the mound can be assigned and released in any order, it can end with 'gaps'. Here is a diagram of the memory distribution of a program that has been running for some time:

Address

Name

Value

2 30

5

(2 30 ) - 1

(2 30 ) - 2

(2 30 ) - 3

42

...

...

...

3

and

(2 30 ) - 3

two

and

42

one

and

42

0

x

2 30

In this case, we have assigned four things on the mound, but we have released two of them. There is a gap between 2 30 and (2 30 ) - 3 that is not currently being used. The specific detail about how and why this happens depends on the strategy used to manage the mound. Different programs can use different 'memory allocators', which are libraries that handle memory allocation for you. Programs in Rust use jemalloc for that purpose.

Anyway, and back to our example. Because this memory is on the mound, it can stay alive longer than the function that creates the box. However, in this case, this does not happen. moving when the function ends, we need to release the activation record from main () . Box <T> , however, does have a trick up its sleeve: Drop . The Drop for Box implementation frees up the memory that has been allocated when the box is created. Great! So when x leaves (leaves context), it first frees allocated memory from the mound:

Address

Name

Value

one

and

42

0

x

??????

moving . We can make memory stay alive longer by transferring ownership, sometimes called 'moving out of the box'. More complex examples will be covered later. ↩ 

Then the activation record goes away, freeing up all of our memory.

Arguments and borrowing

We have done some basic examples with the stack and the mound, but what about function arguments and borrowing? Here's a little Rust program:

fn foo (i: & i32 ) {

let z = 42 ;

}

fn main () {

let x = 5 ;

let y = & x;

foo (y);

}

When we enter main () , the memory looks like this:

Address

Name

Value

one

and

0

0

x

5

x is a simple 5 , and y is a reference to x . So the value of y is the memory address where x lives, which in this case is 0 .

What happens when we call foo () passing to y as an argument?

Address

Name

Value

3

z

42

two

i

0

one

and

0

0

x

5

Activation records are not just for local variables, they are also for arguments. In this case, we need to have both i , our argument, and z our local variable. i is a copy of the argument, and . Because the value of y is 0 then that is the value of i .

This is one reason why borrowing a variable doesn't free up any memory: the reference value is just a pointer to a memory address. If we got rid of the underlying memory, things would not go quite right.

A complex example

Okay, let's go through this complex step-by-step program:

fn foo (x: & i32 ) {

let y = 10 ;

let z = & y ;

baz (z);

bar (x, z);

}

fn bar (a: & i32 , b: & i32 ) {

let c = 5 ;

let d = Box :: new ( 5 );

let e = & d;

baz (e);

}

fn baz (f: & i32 ) {

let g = 100 ;

}

fn main () {

let h = 3 ;

let i = Box :: new ( 20 );

let j = & h;

foo (j);

}

First, we call main () :

Address

Name

Value

2 30

twenty

...

...

...

two

j

0

one

i

2 30

0

h

3

We allocate memory for j , i , and h . i is on the mound, that is why its value points towards it.

Then at the end of main () , foo () is called:

Address

Name

Value

2 30

twenty

...

...

...

5

z

4

4

and

10

3

x

0

two

j

0

one

i

2 30

0

h

3

Space is allocated for x , y , and z . The argument x has the same value as j , because that is what we provided to the function. It is a pointer to address 0 , since j points to h .

Then foo () calls baz () , passing it z :

Address

Name

Value

2 30

twenty

...

...

...

7

g

100

6

F

4

5

z

4

4

and

10

3

x

0

two

j

0

one

i

2 30

0

h

3

We have allocated memory for f and g . baz () is very short, so when it ends, we get rid of your activation record:

Address

Name

Value

2 30

twenty

...

...

...

5

z

4

4

and

10

3

x

0

two

j

0

one

i

2 30

0

h

3

Then foo () calls bar () with x and z :

Address

Name

Value

2 30

twenty

(2 30 ) - 1

5

...

...

...

10

and

9

9

d

(2 30 ) - 1

8

c

5

7

b

4

6

to

0

5

z

4

4

and

10

3

x

0

two

j

0

one

i

2 30

0

h

3

We end up assigning another value on the mound, so we have to subtract one from 2 30 . It is easier to write that than 1,073,741,823 . In any case, we set the variables as usual.

At the end of bar () , this calls baz () :

Address

Name

Value

2 30

twenty

(2 30 ) - 1

5

...

...

...

12

g

100

eleven

F

9

10

and

9

9

d

(2 30 ) - 1

8

c

5

7

b

4

6

to

0

5

z

4

4

and

10

3

x

0

two

j

0

one

i

2 30

0

h

3

With this, we are at our deepest point! Wow! Congratulations on having followed all this and having come so far.

Then baz () ends, we get rid of f and g :

Address

Name

Value

2 30

twenty

(2 30 ) - 1

5

...

...

...

10

and

9

9

d

(2 30 ) - 1

8

c

5

7

b

4

6

to

0

5

z

4

4

and

10

3

x

0

two

j

0

one

i

2 30

0

h

3

Next, we return from bar () . d in this case is a Box <T> , so it also releases what it points to: (2 30 ) - 1.

Address

Name

Value

2 30

twenty

...

...

...

5

z

4

4

and

10

3

x

0

two

j

0

one

i

2 30

0

h

3

Then foo () returns:

Address

Name

Value

2 30

twenty

...

...

...

two

j

0

one

i

2 30

0

h

3

Then finally main () returns, which cleans up the rest. When i is released (via Drop ) it will also clean the rest on the mound.

What do other languages ​​ do?

Most languages ​​with a garbage collector map from the mound by default. This means that all the values ​​are inside boxes (boxed). There are a number of reasons why this is done this way, but they are beyond the scope of this tutorial. Also, there are some optimizations that make this not 100% true all the time. Instead of relying on the stack and Drop to clean the memory, the garbage collector is in charge of managing the mound.

Which to use

If the pile is faster and easier to use, why do we need the mound? A great reason is that allocation from the stack means you only have LIFO semantics to reclaim storage. Allocation from the mound is strictly more general, allowing storage to be taken and returned to the pool in arbitrary order, but at a cost of complexity.

Generally, you should prefer allocation from the stack, which is why Rust assigns from the default stack. The LIFO model of the stack is simpler, on a fundamental level. This has two major impacts: runtime efficiency and semantic impact.

Execution time efficiency.

Managing memory for the stack is trivial: The machine simply increments a single value, the so-called "stack pointer". Memory management for the mound is not: Memory allocated from the mound is released at arbitrary points, and each block of memory allocated from the mound can be arbitrary in size, memory manager generally must work much harder to identify memory that can be reused.

If you would like to dive deeper into this topic in greater detail, this paper is a very good introduction.

Semantic impact

Stack-allocation impacts the Rust language itself, and thus the developer's mental model. The LIFO semantics is what drives how the Rust language handles automatic memory management. Even the deallocation of a uniquely-owned heap-allocated box can be driven by the stack-based LIFO semantics, as discussed throughout this chapter. The flexibility (ie expressiveness) of non LIFO-semantics means that in general the compiler cannot automatically infer at compile-time where memory should be freed; it has to rely on dynamic protocols, potentially from outside the language itself, to drive deallocation (reference counting, as used by Rc and Arc, is one example of this).

Mapping from the stack impacts Rust as a language, and with it the developer's mental model. LIFO semantics is what drives how the Rust language handles automatic memory handling. Even releasing an assigned box from the mound with a single owner can be handled by LIFO semantics, as discussed in this chapter. The flexibility (eg expressiveness) of non-LIFO semantics means that in general the compiler cannot infer automatically and at compile time where memory should be freed; it has to rely on dynamic protocols, potentially external to the language, to perform memory release (reference counting, like the one used in Rc <T> and Arc <T> , is an example).

When taken to the extreme, the greater expressive power of allocation from the mound comes at the cost of either significant runtime support (eg in the form of a garbage collector) or significant effort by the programmer (in the form of explicit manual calls that require verification not provided by the Rust compiler).

Tests

Testing programs can be an effective way to show the presence of bugs, but it is hopelessly inadequate to show their absence. Edsger W. Dijkstra, "The Humble Programmer" (1972)

We will talk about how to test Rust code. What we will not be talking about is the correct way to test Rust code. There are many schools of thought regarding the correct and incorrect way of writing tests. All these approaches use the same basic tools, in this section we will show you the syntax to make use of them.

The test attribute

In essence, a test in Rust is a function that is annotated with the test attribute . We are going to create a new project called adder with Cargo:

$ new adder charge

$ cd adder

Cargo will automatically generate a simple test when you create a new project. Here is the content of src / lib.rs :

#[test]

fn it_works () {

}

Note the # [test] . This attribute indicates that this is a test function. It currently has no body. But that is enough to happen! We can run the tests with test charge :

$ test charge

Compiling adder v0. 1.0 (file: /// Users / goyox86 / Code / rust / adder)

Running target / debug / adder-ba17f4f6708ca3b9

running 1 test

test it_works ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured

Doc-tests adder

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured

Charge I compile and run our tests. There are two sets of output here: one for the tests we write, and one for the documentation tests. We will talk about these later. For now let's see this line:

test it_works ... ok

Note the it_works . It comes from the name of our function:

fn it_works () {

#}

We also get a summary line:

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured

So why do our empty tests pass? Any test that doesn't panic! passes, and any tests that panic! failure. Let's fail our test:

#[test]

fn it_works () {

assert! ( false );

}

assert! it is a macro provided by Rust which takes an argument: if the argument is true , nothing happens. If the argument is false , assert! ago panic! . Let's run our tests again:

$ test charge

Compiling adder v0. 1.0 (file: /// Users / goyox86 / Code / rust / adder)

Running target / debug / adder-ba17f4f6708ca3b9

running 1 test

test it_works ... FAILED

failures:

---- it_works stdout ----

thread 'it_works' panicked at 'assertion failed: false' , src / lib.rs: 3

failures:

it_works

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured

thread '<main>' panicked at 'Some tests failed' , /Users/rustbuild/src/rust-buildbot/slave/stable-dist-rustc-mac/build/src/libtest/lib.rs: 259

Rust tells us that our test has failed:

test it_works ... FAILED

And it is reflected in the summary line:

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured

We also get a non-zero return value:

$ echo $?

101

This is very useful to integrate cargo test with other tools.

We can reverse our test failure with another attribute: should_panic :

#[test]

# [should_panic]

fn it_works () {

assert! ( false );

}

These tests will be successful if we panic! and will fail if completed. Let's try:

$ test charge

Compiling adder v0. 1.0 (file: /// Users / goyox86 / Code / rust / adder)

Running target / debug / adder-ba17f4f6708ca3b9

running 1 test

test it_works ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured

Doc-tests adder

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured

Rust provides another macro, assert_eq! , which compares two arguments to verify equality:

#[test]

# [should_panic]

fn it_works () {

assert_eq! ( "Hello" , "world" );

}

Does this test pass or fail? Due to the presence of the should_panic attribute , it passes:

$ test charge

Compiling adder v0. 1.0 (file: /// Users / goyox86 / Code / rust / adder)

Running target / debug / adder-ba17f4f6708ca3b9

running 1 test

test it_works ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured

Doc-tests adder

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured

Should_panic tests can be fragile, it is difficult to guarantee that the test did not fail for an unexpected reason. To aid in this, an optional parameter expected can be added to the should_panic attribute . The test will ensure that the error message contains the provided message. A safer version of the test would be:

#[test]

# [should_panic (expected = "assertion failed")]

fn it_works () {

assert_eq! ( "Hello" , "world" );

}

That was it for the basics! Let's write a 'real' test:

pub fn sum_two (a: i32) -> i32 {

a + 2

}

#[test]

fn it_works () {

assert_eq! (4, sum_two (2));

}

This is a very common use of assert_eq! : call some function with some known arguments and compare the output of that call with the expected output.

The tests module

There is a way in which our example is not idiomatic: it lacks the tests module . The idiomatic way of writing our example looks like this:

pub fn sum_two (a: i32) -> i32 {

a + 2

}

# [cfg (test)]

mod tests {

use super :: sum_two;

#[test]

fn it_works () {

assert_eq! (4, sum_two (2));

}

}

There are a few changes here. The first is the inclusion of a mod tests with a cfg attribute . The module allows us to group all our tests, and also allows us to define support functions if necessary, all that is not part of our crate. The cfg attribute only compiles our test code if we were trying to run the tests. This can save compilation time, it also ensures that our tests are completely excluded from a normal compilation.

The second change is the use statement . Because we are in an internal module, we need to make our test available within our current scope. This can be annoying if you have a large module, and that is why the use of the glob facility is common . Let's change our src / lib.rs to make use of it:

pub fn sum_two (a: i32) -> i32 {

a + 2

}

# [cfg (test)]

mod tests {

use super :: *;

#[test]

fn it_works () {

assert_eq! (4, sum_two (2));

}

}

Note the line use different. Now we run our tests:

$ test charge

Compiling adder v0. 1.0 (file: /// Users / goyox86 / Code / rust / adder)

Running target / debug / adder-ba17f4f6708ca3b9

running 1 test

test tests :: it_works ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured

Doc-tests adder

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured

Works!

The current convention is to use the tests module to hold your "unit-style" tests. Anything that just tests a small piece of functionality goes here. But what about "integration-style" testing? For them, we have the tests directory .

The tests directory

To write an integration test, let's create a tests directory and put a tests / lib.rs file inside, with the following content:

extern crate adder;

#[test]

fn it_works () {

assert_eq! (4, adder :: sum_two (2));

}

It looks similar to our previous tests, but slightly different. Now we have an extern crate adder at the beginning. This is because the tests in the directory are a separate crate, so we must import our library. This is also why tests is a great place to write integration tests: these tests use the library just like any other consumer would.

Let's run them:

$ test charge

Compiling adder v0. 1.0 (file: /// Users / goyox86 / Code / rust / adder)

Running target / debug / lib -f 71036151ee98b04

running 1 test

test it_works ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured

Running target / debug / adder-ba17f4f6708ca3b9

running 1 test

test tests :: it_works ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured

Doc-tests adder

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured

We now have three sections: Our previous tests were also run, along with the new integration test.

That was it for the tests directory . The tests module is not necessary here, since the complete module is dedicated to tests.

Finally let's take a look at that third section: documentation tests.

Documentation tests

Nothing is better than documentation with examples. Nothing is worse than examples that don't work, because the code has changed since the documentation was written. Regarding this, Rust supports the automatic execution of the examples present in your documentation. Here's a polished src / lib.rs with examples:

//! The `adder` crate provides functions that add numbers to other numbers.

//!

//! # Examples

//!

//!

//! assert_eq! (4, adder :: add_two (2)); //! `` ''

/// This function adds two to its argument. /// /// # Examples /// /// /// use adder :: add_two; /// /// assert_eq! (4, add_two (2)); /// pub fn add_two (a: i32) -> i32 {a + 2}

[cfg (test)]

mod tests {use super :: *;

#[test]

fn it_works () {

assert_eq! (4, add_two (2));

}

}

Note the module-level documentation with `//!` And the function-level documentation with `///`. Rust documentation supports Markdown in comments and treble (\ `\` \ `) delimit code blocks. It is conventional to include the `# Examples` section, exactly like this, followed by the examples.

Let's run the tests again:

bash

$ test charge

Compiling adder v0.1.0 (file: /// Users / goyox86 / Code / rust / adder)

Running target / debug / lib-f71036151ee98b04

running 1 test

test it_works ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured

Running target / debug / adder-ba17f4f6708ca3b9

running 1 test

test tests :: it_works ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured

Doc-tests adder

running 2 tests

test _0 ... ok

test sum_two_0 ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured

Now we have all three types of tests running! Note the names of the documentation tests: _0 is generated for the module test, and sum_two_0 for the function test. These numbers will auto increment with names like sum_two_1 as more examples are added.

Conditional Compilation

Rust has a special attribute, # [cfg] , that allows you to compile code based on an option provided to the compiler. It has two forms:

# [cfg (foo)]

# fn foo () {}

# [cfg (bar = "baz")]

# fn bar () {}

It also has some helpers:

# [cfg (any (unix, windows))]

# fn foo () {}

# [cfg (all (unix, target_pointer_width = "32"))]

# fn bar () {}

# [cfg (not (foo))]

# fn not_foo () {}

Which can be nested arbitrarily:

# [cfg (any (not (unix), all (target_os = "macos", target_arch = "powerpc")))]

# fn foo () {}

To activate or deactivate these switches, if you are using Cargo, they are configured in the [features] section of your Cargo.toml

[features]

# No features by default

default = []

# The “secure-password” feature depends on the bcrypt package.

secure-password = [ "bcrypt" ]

When we do this, Cargo passes an option to rustc :

--cfg feature = "$ {feature_name}"

The sum of those cfg options will determine which are activated, and consequently, which code will be compiled. Let's take this code:

# [cfg (feature = "foo")]

mod foo {

}

If we compile it with cargo build --features "foo" , Cargo will send the option --cfg feature = "foo" to rustc , and the output will have the mod foo . If we compile with a normal build charge , no extra option will be provided, and because of this, no foo module will exist.

cfg_attr

You can also configure another attribute based on a cfg variable with cfg_attr :

# [cfg_attr (a, b)]

# fn foo () {}

It will be the same as # [b] if a is configured by a cfg attribute , and nothing else.

cfg!

The syntax extension also allows you to use this type of options at any other point in your code:

if cfg! (target_os = "macos" ) || cfg! (target_os = "ios" ) {

println! ( "Think Different!" );

}

These will be replaced by true or false at compile time, depending on configuration options.

Documentation

Documentation is an important part of any software project and a first-class citizen at Rust. Let's talk about the tools that Rust provides to document your projects.

About rustdoc

The Rust distribution includes a tool, rustdoc , in charge of generating the documentation. rustdoc is also used by Cargo through cargo doc .

Documentation can be generated in two ways: from source code, or from Markdown files.

Documenting source code

The main way to document a Rust project is through the annotation of the source code. For this purpose, you can use documentation comments:

/// Build a new `Rc`.

///

/// # Examples

///

///

/// use std :: rc :: Rc; /// /// let five = Rc :: new (5); /// `` `pub fn new (value: T) -> Rc {// the implementation goes here}

The code above generates documentation that looks like [this] [rc-new] (English). I've left the implementation out, with a regular comment instead. That's the first thing to note about this annotation: use `///`, instead of `//`. The triple slash indicates that it is a documentation comment.

Documentation comments are written in Markdown format.

Rust maintains a record of such comments, a record that he uses when generating documentation. This is important when documenting things like enums:

`` 'rust

/// The type `Option`. See [module level documentation] (../) for more information.

enum Option <T> {

/// No value

None

/// Some `T` value

Some (T),

}

The above works, but this does not:

/// The type `Option`. See [module level documentation] (../) for more information.

enum Option {

None, /// No value

Some (T), /// Some value `T`

}

You will get an error:

hello.rs:4:1: 4: 2 error: expected ident, found `}`

hello.rs:4}

^

Writing documentation comments

Either way, let's cover each part of this comment in detail:

/// Build a new `Rc <T>`.

# fn foo () {}

The first line of a documentation comment should be a short summary of its functionality. A sentence. Only the basics. High level.

///

/// Other details about the construction of `Rc <T>` s, perhaps describing semantics

/// complicated, maybe additional options, anything extra.

///

# fn foo () {}

Our original example only had one summary line, but if we had more to say, we could have added more explanation in a new book.

Special sections

/// # Examples

# fn foo () {}

Below are the special sections. These are indicated with a header, # . There are three types of header that are commonly used. These are not special syntax, just convention, for now.

/// # Panics

# fn foo () {}

Bad and unrecoverable uses of a function (eg programming errors) in Rust are usually indicated by panics, which at least kill the current thread. If your role has a non-trivial contract like this, which is panicked / enforced, documenting it is very important.

/// # Failures

# fn foo () {}

If your function or method returns a Result <T, E> , then describing the conditions under which Err (E) returns is a good thing to do. This is slightly less important than Panics , as it is encoded in the type system, but it is still something to do.

/// # Safety

# fn foo () {}

If your function is unsafe (insecure), you should explain what are the invariants that must be maintained by the caller.

/// # Examples

///

///

/// use std :: rc :: Rc; /// /// let five = Rc :: new (5); /// `` `

fn foo () {}

Third, `Examples`, include one or more examples of the use of your function or method, and your users will love you. These examples go inside code block annotations, which we will talk about in a moment, they can have more than one section:

`` 'rust

/// # Examples

///

/// Simple `& str` patterns:

///

///

/// let v: Vec <& str> = "Mary had a little lamb" .split ('') .collect (); /// assert_eq! (v, vec! ["Mary", "had", "a", "little lamb"]); /// /// /// More complex patterns with lambdas: /// /// /// let v: Vec <& str> = "abc1def2ghi" .split (| c: char | c.is_numeric ()). collect (); /// assert_eq! (v, vec! ["abc", "def", "ghi"]); /// `` `

fn foo () {}

Let's discuss the details of those code blocks.

#### Code block annotations

To write some Rust code in a comment, use triple bass:

`` 'rust

///

/// println! ("Hello, world"); /// `` `

fn foo () {}

If you want code other than Rust, you can add an annotation:

`` 'rust

/// `` `c

/// printf ("Hello, world \ n");

///

fn foo () {}

The syntax of this section will be highlighted according to the language you are showing. If you are only displaying plain text, use `text`.

Here, it is important to choose the correct annotation, because `rustdoc` uses it in an interesting way: It can be used to test your examples, so that they do not become obsolete over time. If you have any C code but `rustdoc` thinks it's Rust, it's because you forgot the annotation,` rustdoc` complained when trying to generate the documentation.

## Documentation as evidence

Let's discuss our sample documentation:

`` 'rust

///

/// println! ("Hello, world"); /// `` `

fn foo () {}

You'll notice that you don't need a `fn main ()` or something else. `rustdoc` will automatically add a main () around your code, and in the right place. For example:

`` 'rust

///

/// use std :: rc :: Rc; /// /// let five = Rc :: new (5); /// `` `

fn foo () {}

It will become the test:

`` 'rust

fn main () {

use std :: rc :: Rc;

let five = Rc :: new (5);

}

Here is the complete algorithm that rustdoc uses to post-process the examples:

                   Any leftover #! [Foo] attributes are left intact as a crate attribute.

                   Some common attributes are inserted, including unused_variables , unused_assignments , unused_mut , unused_attributes , and dead_code . Small examples occasionally trigger these lints.

                   If the example does not contain extern crate , then the extern crate <micrate>; is inserted.

                   Finally, if the example does not contain fn main , the text is wrapped in fn main () {your_code}

Sometimes this is not enough. For example, all these code examples with /// that we've been talking about? Plain text:

/// Some documentation.

# fn foo () {}

Looks different on departure:

/// Some documentation.

# fn foo () {}

Yes, it is correct: you can add lines that start with # , and these will be removed from the output, but they will be used in the compilation of your code. You can use this to your advantage. In this case, the documentation comments need to apply to some kind of function, so if I want to show just one documentation comment, I need to add a little function definition below. At the same time, it is there just to satisfy the compiler, so hiding it makes the example cleaner. You can use this technique to explain longer examples in detail, while preserving your documentation's ability to be tested. For example, this code:

let x = 5 ;

let y = 6 ;

println! ( "{}" , x + y);

Here's an explanation, rendered:

First, we assign x the value of five:

let x = 5 ;

# let y = 6 ;

# println! ( "{}" , x + y);

Next, we assign six to y :

# let x = 5 ;

let y = 6 ;

# println! ( "{}" , x + y);

Finally, we print the sum of x and y :

# let x = 5 ;

# let y = 6 ;

println! ( "{}" , x + y);

Here's the same explanation, in plain text:

First, we assign x the value of five:

let x = 5;

# let y = 6;

# println! ("{}", x + y);

Next, we assign six to y :

# let x = 5;

let y = 6;

# println! ("{}", x + y);

Finally, we print the sum of x and y :

# let x = 5;

# let y = 6;

println! ("{}", x + y);

By repeating all parts of the example, you can ensure that your example still compiles, showing only the parts relevant to your explanation.

Documenting macros

Here is an example of documentation to a macro:

/// Panic with a provided message unless the expression is evaluated to true.

///

/// # Examples

///

///

/// # # [macro_use] extern crate foo; /// # fn main () {/// panic_unless! (1 + 1 == 2, "The mathematics is broken."); /// #} /// /// /// should_panic /// # # [macro_use] extern crate foo; /// # fn main () {/// panic_unless! (true == false, "I am broken."); /// #} /// `` `

[macro_export]

macro_rules! panic_unless {($ condition: expr, $ ($ rest: expr), +) => ({if! $ condition {panic! ($ ($ rest), +);}}); }

fn main () {}

You will notice three things: we need to add our own `extern crate` line, so that we can add the` # [macro_use] `attribute. Second, we will need to add our own `main ()`. Finally, a judicious use of `#` to comment on those two things, so that they are shown in the output.

### Running documentation tests

To run the tests you can:

bash

$ rustdoc --test path / to / my / crate / root.rs

# or

$ test charge

Correct, charge test tests embedded documentation as well. However, I load test , it won't test binary crates, just libraries. This is because of the way rustdoc works: it links to the library to be tested, but in the case of a binary, there is nothing to link to.

There are a few more annotations that are useful to help rustdoc do the right thing when testing your code:

/// `` `ignore

/// fn foo () {

///

fn foo () {}

The `ignore` directive tells Rust to ignore the code. This is the form that you will almost never want, as it is the most generic. Instead, consider scoring with `text` if it is not code, or use` # `s to get a working example that only shows the part that interests you.

`` 'rust

/// `` `should_panic

/// assert! (false);

///

fn foo () {}

`should_panic` tells` rustdoc` that the code must compile correctly, but without the need to successfully pass a test.

`` 'rust

/// `` `no_run

/// loop {

/// println! ("Hello, world");

///}

///

fn foo () {}

The `no_run` attribute will compile your code, but will not execute it. This is important for examples like "Here's how to start a network service," which you should make sure compiles, but it could cause an infinite loop!

### Documenting modules

Rust has another type of documentation comment, `//!`. This comment does not document the next item, it comments the item that encloses it. In other words:

`` 'rust

mod foo {

//! This is documentation for the `foo` module.

//!

//! # Examples

// ...

}

This is where you will see //! most often used: for module documentation. If you have a module in foo.rs , frequently when you open its code you will see this:

//! A module to use `foo`s.

//!

//! The `foo` module contains a lot of functionality blah blah blah

Documentation feedback style

Other documentation

All of this behavior works on non-Rust files as well. Because comments are written in Markdown, they are often .md files .

When you write documentation to Markdown files, you don't need to prefix the documentation with comments. For example:

/// # Examples

///

///

/// use std :: rc :: Rc; /// /// let five = Rc :: new (5); /// `` `

fn foo () {}

it's just

~~~ markdown

# Examples

use std :: rc :: Rc;

let five = Rc :: new (5);

~~~

when it is in a Markdown file. There is only one detail, markdown files need to have a title like this:

markdown

% Title

This is the sample documentation

This line % must be located in the first line of the file.

doc attributes

At a deeper level, documentation comments are another way to write documentation attributes:

/// Este

# fn foo () {}

# [doc = "this"]

# fn bar () {}

they are the same as these:

//! Este

#! [doc = "/// this"]

You will not frequently see this attribute being used to write documentation, but it can be useful when you are changing certain options, or writing a macro.

Re-exports

rustdoc will show the documentation for a public re-export in both places:

extern crate foo;

pub use foo :: bar;

The above will create documentation for the bar within the documentation for the crate foo , as well as the documentation for your crate. It will be the same documentation in both places.

This behavior can be suppressed with no_inline :

extern crate foo;

# [doc (no_inline)]

pub use foo :: bar;

Controlling HTML

You can control some aspects of the HTML that rustdoc generates through the #! [Doc] version of the attribute:

#! [doc (html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",

html_favicon_url = "http://www.rust-lang.org/favicon.ico",

html_root_url = "http://doc.rust-lang.org/")]

This sets up a few options, with a logo, favicon, and root URL.

Generation options

rustdoc also contains a few options on the command line, for further customization:

--html-in-header FILE : includes the FILE content at the end of the <head> ... </head> section .

--html-before-content FILE : includes the FILE content after <body> , before the rendered content (including the search bar).

--html-after-content FILE - Includes the FILE content after all rendered content.

Safety note

Markdown in documentation comments is set raw on the final page. Be careful with literal HTML:

/// <script> alert (document.cookie) </script>

# fn foo () {}

Iterators

Let's talk about cycles.

Remember the Rust for cycle ? Here is an example:

for x in 0..10 {

println! ("{}", x);

}

Now that you know more about Rust, we can talk in detail about how this code works. The ranges ( 0..10 ) are iterators. An iterator is something we can call the .next () method on repeatedly, and the iterator provides us with a sequence of elements.

For example:

let mut range = 0..10;

loop {

match rank.next () {

Some (x) => {

println! ("{}", x);

},

None => {break}

}

}

We create a link (a variable) to the range, our iterator. Then we iterate through the loop cycle , with an internal match . This match uses the result of rank.next () , which gives us a reference to the next value in the iterator. next returns an Option <i32> , in this case, which will be Some (i32) when we have a value and None when we run out of values. If we get Some (i32) , we print it, and if we get None , we break the loop, leaving it through break .

This code example is basically the same as our version of a for for loop . The for loop is just a practical way to write a loop / match / break construct .

However, for loops are not the only thing that uses iterators. Write your own iterator involves implementing the Trait Iterator . While doing so is outside the scope of this guide, Rust provides a number of useful iterators to accomplish various tasks. Before talking about that, we should talk about an anti-patron. Said anti-pattern is to use ranges in the manner outlined above.

Yes, we just talked about how cool the ranges are. But they are also very primitive. For example, if we need to iterate through the content of a vector, we might be tempted to write something like this:

let nums = vec! [1, 2, 3];

for i in 0..nums.len () {

println! ("{}", nums [i]);

}

This is not strictly worse than using an iterator. You can iterate into vectors directly, write this:

let nums = vec! [1, 2, 3];

for num in & nums {

println! ("{}", num);

}

There are two reasons for doing it this way. First, express what we want more directly. We iterate through the entire vector, instead of iterating through indexes and then indexing the vector. Second, this version is more efficient: in the first version we will have checks of extra limits because it uses indexing, nums [i] . In the second example and because with the iterator we give a reference to each element at the same time, there is no limit check. This is very common in iterators: we can ignore unnecessary limit checks, while knowing that we are safe.

There is another detail here that is not 100% clear due to println operation ! . num is of type & i32 . A reference to an i32 , not an i32 . println! handles dereferencing for us, that's why we don't see it. This code is also correct:

let nums = vec! [1, 2, 3];

for num in & nums {

println! ("{}", * num);

}

Now we are referencing num explicitly. Why & nums give us references? Firstly, because we explicitly request it with & . Second, if it gave us the data itself, we would have to own it, which would involve creating a copy of the data and then giving us that copy. With references, we are only borrowing a reference to the data, and therefore we only pass a reference, without the need to transfer the membership.

So now that we've established that ranges are sometimes not what we want, let's talk about what we want.

There are three broad classes of things that are relevant: iterators, iterator adapters, and consumers . Here are some definitions:

Iterators provide a sequence of values.

Iterator adapters operate on one iterator, producing a new iterator with a different sequence of output.

Consumers operate on an iterator, producing a final set of values.

Let's talk about consumers first, because we've already seen an iterator, the ranges.

Consumers

A consumer operates on an iterator, returning some kind of value or values. The most common consumer is collect () . This code does not compile, but shows the intention:

let uno_hasta_cien = (1..101) .collect ();

As you can see we call collect () on the iterator. collect () takes as many values ​​as the iterator provides, returning a collection of results. So why doesn't this code compile? Rust can't determine what kinds of things you want to collect, and that's why you need to let him know. This is the version that compiles:

let uno_hasta_cien = (1..101) .collect :: <Vec <i32>> ();

If you remember, the syntax :: <> allows you to give a hint about the type, in our case we are saying that we want a vector of integers. It is not always necessary to use the full type. _ will allow you to give a partial hint about the type:

let one_up to one hundred = (1..101) .collect :: <Vec <_>> ();

This says "Collect in a Vec <T> , please, but infer that it's T for me.". _ is for this reason sometimes called a "type placeholder".

collect () is the most common consumer, but there are others. find () is one of them:

let mayor_a_cuarenta_y_dos = (0..100)

.find (| x | * x> 42);

match mayor_a_cuarenta_y_dos {

Some (_) => println! ("We have some numbers!"),

None => println! ("No numbers were found :("),

}

find receives a closure, and works on a reference to each element of an iterator. Said closure returns true if the element is the one we are looking for and false otherwise. Because we might not find an element that meets our criteria, find returns an Option instead of an element.

Another major consumer is fold . It looks like this:

let sum = (1..4) .fold (0, | sum, x | sum + x);

fold (base, | accumulator, element | ...) . It takes two arguments: the first is an element called base . The second is a closure which in turn takes two arguments: the first is called the accumulator , and the second is an element . In each iteration, the closure is called, and the result is used as the accumulator value in the next iteration. In the first iteration, the base is the accumulator value.

Well that's a little confusing. Let's examine the values ​​of all things in this iterator:

base

accumulator

element

closure result

0

0

one

one

0

one

two

3

0

3

3

6

We have called fold () with these arguments:

# (1..4)

.fold (0, | sum, x | sum + x);

So 0 is our base, sum is our accumulator, and x is our element. In the first iteration, we assign sum to 0 and x is the first element in our range, 1 . Then we add sum and x , which gives us 0 + 1 = 1 . In the second iteration, that value becomes the value of our accumulator, sum , and the element is the second element in the range, 2 . 1 + 2 = 3 and likewise becomes the accumulator value for the last iteration. In that iteration, x is the last element, 3 , and 3 + 3 = 6 , the final result for our sum . 1 + 2 + 3 = 6 , that's the result we get.

Whew. Fold may be a little strange at first glance, but once it clicks, you can use it everywhere. Whenever you have a list of things, and need a single result, fold is appropriate.

Consumers are important because of an additional property of iterators that we haven't talked about yet: laziness. Let's talk more about iterators and you'll see why consumers are important.

Iterators

As we've said before, an iterator is something we can call the .next () method on repeatedly, and it returns a sequence of elements. Because we need to call the method, the iterators can be lazy and not generate all the values ​​in advance. This code, for example, does not generate numbers 1-99 . Instead it creates a value that represents the sequence:

let nums = 1..100;

Because we did nothing with the range, it did not generate the sequence. Let's add a consumer:

let nums = (1..100) .collect :: <Vec <i32>> ();

Now collect () will require the range to supply some numbers, and therefore will have to do the work of generating the sequence.

Ranges are one of the two basic forms of iterators that you will see. The other is iter () . iter () can transform a vector into a simple iterator that provides one element at a time:

let nums = vec! [1, 2, 3];

for num in nums.iter () {

println! ("{}", num);

}

These two basic iterators should be useful. There are more advanced iterators, including those that are infinite.

Enough about iterators, Iterator Adapters are the last concept related to Iterators that we should mention. Let's do it!

Iterator adapters

The adapters iterators take an iterator and modify it in some way, producing a new one. The simplest is called map :

(1..100) .map (| x | x + 1);

map is called in another iterator, map produces a new iterator in which each reference to an element has the closure that has been provided as an argument. The previous code will give us the numbers 2-100 , well, almost! If you compile the example, you will get a warning:

warning: unused result which must be used: iterator adapters are lazy and

do nothing unless consumed, # [warn (unused_must_use)] on by default

(1..100) .map (| x | x + 1);

^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Laziness strikes again! That closure will never run. This example does not print any number:

(1..100) .map (| x | println! ("{}", X));

If you are trying to run a closure in an iterator to get its side effects use a for .

for i in (1 ..). take (5) {

println! ("{}", i);

}

This will print:

one

two

3

4

5

filter () is an adapter that takes a closure as an argument. This closure returns true or false . The new iterator that filter () produces only elements for which the closure returns true :

for i in (1..100) .filter (| & x | x% 2 == 0) {

println! ("{}", i);

}

This will print all even numbers between one and one hundred. (Note that because filter does not consume the elements being iterated, a reference to each element is passed to it, therefore, the predicate uses the & x pattern to extract the integer.)

(one..)

.filter (| & x | x% 2 == 0)

.filter (| & x | x% 3 == 0 )

.take (5)

.collect :: <Vec <i32>> ();

The above gives you a vector containing 6 , 12 , 18 , 24 , and 30 .

This is a small sample of the things that iterators, iterator adapters, and consumers can help you with. There are a variety of really useful iterators, along with the fact that you can write your own. Iterators provide a safe and efficient way to manipulate all kinds of lists. They are a bit unusual at first glance, but if you play around with them a bit, you will be hooked

Concurrence

Concurrency and parallelism are two incredibly important topics in computer science, they are also a hot topic in the industry today. Computers increasingly have more and more cores, even so, still some developers are not ready to fully use us.

Rust's memory management security also applies to its concurrency history. Even concurrent programs must be memory-safe, with no race conditions. Rust's type system is up to the challenge, and gives you powerful avenues to reason about concurrent code at compile time.

Before we talk about the concurrency features that come with Rust, it's important to understand something: Rust is low-level, low enough to the point that all of these facilities are implemented in the standard library. This means that if you don't like some aspect of the way Rust handles concurrency, you can implement an alternative way of doing it. Mine is a vivid example of this principle in action.

Threads

The standard Rust library provides a library for thread handling, which allows you to run rust code in parallel. Here is a basic example of using std :: thread :

use std :: thread;

fn main () {

thread :: spawn (|| {

println! ( "Hello from a thread!" );

});

}

The thread :: spawn () method accepts a closure as an argument, a closure that is executed on a new thread. thread :: spawn () returns a handle to the new thread, which can be used to wait for the thread to finish and then extract its result:

use std :: thread;

fn main () {

let handle = thread :: spawn (|| {

"Hello from a thread!"

});

println! ( "{}" , handle.join (). unwrap ());

}

Many languages ​​possess the ability to run threads, but it is wildly insecure. There are whole books on how to prevent mistakes that occur as a result of sharing mutable state. Rust helps with its rate system, preventing race conditions at compile time. Let's talk about how you can effectively share things between threads.

Mutable State Shared Safe

Due to Rust's type system, we have a concept that sounds like a lie: "safe shared mutable state". Many developers agree that the shared mutable state is very, very bad.

Someone once said:

The shared mutable state is the root of all evil. Most languages ​​try to deal with this problem through the 'mutable' part, but Rust confronts it by solving the 'shared' part.

The same membership that prevents improper use of pointers also helps eliminate race conditions, one of the worst concurrency-related bugs.

As an example, a Rust program that would have a career condition in many languages. In Rust I would not compile:

use std :: thread;

fn main () {

let mut data = vec! [1u32, 2, 3];

for i in 0..3 {

thread :: spawn (move || {

data [i] + = 1;

});

}

thread :: sleep_ms (50);

}

The above produces an error:

8:17 error: capture of moved value: `data`

data [i] + = 1;

^ ~~~

In this case, we know that our code should be safe, but Rust is not sure about it. It is not really safe, if we had a reference to data in each thread, and the thread belongs to the reference, we would have three owners! That is wrong. We can fix this by using the Arc <T> type , a pointer with atomic reference count. The 'atomic' part means that it is safe to share between threads.

Arc <T> assumes one more property about its content to ensure that it is safe to share between threads: it assumes that its content is Sync . But in our case, we want to mutate the value. We need a guy who can make sure that only one person at a time can mutate what's inside. For that, we can use the Mutex <T> type . Here is the second version of our code. It still doesn't work, but for a different reason:

use std :: thread;

use std :: sync :: Mutex;

fn main () {

let mut data = Mutex :: new (vec! [1u32, 2, 3]);

for i in 0..3 {

let data = data.lock (). unwrap ();

thread :: spawn (move || {

data [i] + = 1;

});

}

thread :: sleep_ms (50);

}

Here is the error:

: 9: 9: 9:22 error: the trait `core :: marker :: Send` is not implemented for the type` std :: sync :: mutex :: MutexGuard <'_, collections :: vec :: Vec> `[E0277]

: 11 thread :: spawn (move || {

^ ~~~~~~~~~~~~

: 9: 9: 9:22 note: `std :: sync :: mutex :: MutexGuard <'_, collections :: vec :: Vec>` cannot be sent between threads safely

: 11 thread :: spawn (move || {

^ ~~~~~~~~~~~~

You see, Mute x has a loc k method that has this signature:

fn lock (& ​​self) -> LockResult <mutexguard>

</ mutexguard

Because Send is not implemented for MutexGuard <T> , we cannot transfer MutexGuard <T> between threads, which results in the error.

We can use Arc <T> to correct the error. Here is the functional version:

use std :: sync :: {Arc, Mutex};

use std :: thread;

fn main () {

let data = Arc :: new (Mutex :: new ( vec! [ 1u32 , 2 , 3 ]));

for i in 0 .. 3 {

let data = data.clone ();

thread :: spawn (move || {

let mut data = data.lock (). unwrap ();

data [i] + = 1 ;

});

}

thread :: sleep_ms ( 50 );

}

Now we call clone () on our Arc , which increments the internal counter. This handle is then moved into the new thread. Let's examine the thread body more closely:

# use std :: sync :: {Arc, Mutex};

# use std :: thread;

# fn main () {

#     let data = Arc :: new (Mutex :: new ( vec! [ 1u32 , 2 , 3 ]));

#     for i in 0 .. 3 {

#         let data = data.clone ();

thread :: spawn (move || {

let mut data = data.lock (). unwrap ();

data [i] + = 1 ;

});

#}

# thread :: sleep_ms ( 50 );

#}

First, we call lock () , which gets the mutual exclusion lock. Because this operation may fail, this method returns a Result <T, E> , and because this is just an example, we do unwrap () on it to get a reference to the data. Real code would have more robust error handling here. We are free to mutate it, since we have the blockade.

Lastly, while the threads run, we wait for the culmination of a short timer. This is not ideal: we could have chosen a reasonable time to wait, but we will most likely wait longer than necessary, or not long enough, depending on how long the threads take to finish computing when the program runs.

A more precise alternative to the timer would be to use one of the mechanisms provided by the Rust standard library for inter-thread synchronization. Let's talk about them: the channels.

Channels

Here is a version of our code that uses channels for synchronization, instead of waiting for a specific time:

use std :: sync :: {Arc, Mutex};

use std :: thread;

use std :: sync :: mpsc;

fn main () {

let data = Arc :: new (Mutex :: new ( 0u32 ));

let (tx, rx) = mpsc :: channel () ;

for _ in 0 .. 10 {

let (data, tx) = (data.clone (), tx.clone ());

thread :: spawn (move || {

let mut data = data.lock (). unwrap ();

* data + = 1 ;

tx.send (());

});

}

for _ in 0 .. 10 {

rx.recv ();

}

}

We use the mpsc :: channel () method to build a new channel. We send (via send ) a single () through the channel, and then wait for the return of ten of them.

While this channel is only sending a generic signal, we can send any data that is Send through the channel!

use std :: thread;

use std :: sync :: mpsc;

fn main () {

let (tx, rx) = mpsc :: channel ();

for _ in 0 .. 10 {

let tx = tx.clone () ;

thread :: spawn (move || {

let answer = 42u32 ;

tx.send (response);

});

}

rx.recv (). ok (). expect ( "The response could not be received" );

}

A u32 is Send because we can make a copy of it. So we create a thread, and ask it to calculate the response, it then sends the response back to us (using send () ) through the channel.

Panics

A panic! will cause an abrupt termination of the current thread. You can use Rust threads as a simple isolation mechanism:

use std :: thread;

let result = thread :: spawn (move || {

panic! ( "ups!" );

}). join ();

assert! (result.is_err ());

Our Thread returns a Result , which allows us to check if the thread has panicked or not.

Error Handling

The plans best established by mice and men often go awry. "Tae a Moose", Robert Burns

Sometimes things just go wrong. It is important to have a plan for when the inevitable happens. Rust has rich support for handling errors that could (let's be honest: will occur) occur in your programs.

There are two types of errors that can occur in your programs: failure and panic. We will talk about the differences between the two, and then we will discuss how to handle each one. Then we will discuss how to promote panic flaws.

Fail vs. Panic

Rust uses two terms to differentiate between the two forms of error: failure, and panic. A failure is an error from which we can somehow recover. A panic is an unrecoverable mistake.

What do we mean by "recover"? Well, in most cases, the possibility of an error is expected. For example, consider the parse function :

"5" .parse ();

This method converts a character string to another type. But because it is a character string, you cannot be sure that the conversion will actually be successful. For example, what should this be converted to ?:

"hello5world" .parse ();

The above does not work. We know that the parse () method will only be successful for some entries. It is expected behavior. This is why we call this error a failure .

On the other hand, sometimes there are errors that are unexpected, or from which we cannot recover. A classic example is an assert! :

# let x = 5 ;

assert! (x == 5 );

We use assert! to declare that something is true (true). If the statement is not true, then something is very wrong. Bad enough that you cannot continue running in the current state. Another example is using the unreachable! () Macro :

use Event :: NewLaunch;

enum Event {

New Launch,

}

fn probability (_: & Event) -> f64 {

// the actual implementation would be more complex, of course

0.95

}

fn descriptive_probability (event: Event) -> & 'static str {

match probability (& event) {

1.00 => "true",

0.00 => "impossible",

0.00 ... 0.25 => "very unlikely",

0.25 ... 0.50 => "unlikely",

0.50 ... 0.75 => "probable",

0.75 ... 1.00 => "very likely",

}

}

fn main () {

println! ("{}", descriptive_probability (NewLaunch));

}

The above will result in an error:

error: non-exhaustive patterns: `_` not covered [E0004]

While we know we have covered every possible case, Rust may not know. You don't know what the probability is between 0.0 and 1.0. That is why we add another case:

use Event :: NewLaunch;

enum Event {

New Launch,

}

fn probability (_: & Event) -> f64 {

// the actual implementation would be more complex, of course

0.95

}

fn probabilidad_descriptiva (event: Event) -> & 'static str {

match probability (& event) {

1.00 => "true" ,

0.00 => "impossible" ,

0.00 ... 0.25 => "very unlikely" ,

0.25 ... 0.50 => "unlikely" ,

0.50 ... 0.75 => "probable" ,

0.75 ... 1.00 => "very likely" ,

_ => unreachable! ()

}

}

fn main () {

println! ( "{}" , descriptive_probability (NewLaunch));

}

We should never reach case _ , because of this we use the macro to indicate it. unreachable! () produces a different type of error than Result . Rust calls those kinds of mistakes panic .

Handling errors with Option and Result

The simplest way to indicate that a function may fail is using the Option <T> type . For example, the find method on strings tries to locate a pattern in the string, it returns an Option :

let s = "foo" ;

assert_eq! (s.find ( 'f' ), Some ( 0 ));

assert_eq! (s.find ( 'z' ), None );

This is appropriate for simple cases, but does not give us much information in the event of a failure. What if we wanted to know why the function failed? For this, we can use the type Result <T, E> . What it looks like:

enum Result <T, E> {

Ok (T),

Err (E)

}

This enum is provided by Rust, that's why you don't need to define it if you want to use it. The Ok (T) variant represents success, and the Err (E) variant represents a failure. Returning a Result instead of an Option is recommended for most non-trivial cases:

Here is an example of using Result :

# [derive (Debug)]

enum Version {Version1, Version2}

# [derive (Debug)]

enum ErrorParseo {InvalidHeadLength, Invalid Version}

fn parsear_version (header: & [ u8 ]) -> Result <Version, ErrorParseo> {

if header.len () < 1 {

return Err (ErrorParseo :: InvalidHeadLength) ;

}

header match [ 0 ] {

1 => Ok (Version :: Version1),

2 => Ok (Version :: Version2),

_ => Err (ErrorParseo :: InvalidHeadLength)

}

}

fn main () {

let version = parsear_version (& [ 1 , 2 , 3 , 4 ]);

match version {

Ok (v) => {

println! ( "working with version: {:?}" , v);

}

Err (e) => {

println! ( "head parsing error: {:?}" , e);

}

}

}

This function makes use of an enum, ErrorParseo , to list the errors that can occur.

The Debu g trait is the one that allows us to print the enum value using the {:?} Format operation .

Unrecoverable errors with panic!

In the event of an unexpected error from which it cannot be recovered, the macro panic! It is used to induce panic. Such a panic will abruptly end the current thread of execution providing an error message:

panic! ("boom");

It results in

thread '' panicked at 'boom', hello.rs:2

when you run it.

Because these situations are relatively rare, use panics sparingly.

Promoting panic failures

In certain circumstances, even knowing that a function may fail, we may want to treat the failure as a panic. For example, io :: stdin (). Read_line (& mut buffer) returns a Result <usize> , when there is an error reading the line. This allows us to manage and possibly recover in case of error.

If we don't want to handle the error, and instead just abort the program, we can use the unwrap () method :

io :: stdin (). read_line (& mut buffer) .unwrap ();

unwrap () will panic ( panic! ) if the Result is Err . This basically says "Give me the value, and if something goes wrong, just abort the execution." This is less reliable than matching the error and trying to recover, but at the same time it is significantly shorter. Sometimes abrupt termination is appropriate.

There is a way to do the above that is a little better than unwrap () :

let mut buffer = String :: new ();

let read_bytes = io :: stdin (). read_line (& mut buffer)

.okay()

.expect ("Failed to read line");

ok () converts the Result to an Option , and expect () does the same as unwrap () , but receives a message as an argument. This message is passed to the panic! underlying, providing a better error message.

Using try!

When we write code that calls many functions that return the Result type , error handling can be tedious. The try macro ! It hides some of the repetitive code for error propagation in the call stack.

try! replaces:

use std :: fs :: File;

use std :: io;

use std :: io :: prelude :: *;

struct info {

name: String ,

age: i32 ,

grade: i32 ,

}

fn escribir_info (info: & Info) -> io :: Result <()> {

let mut file = File :: create ( "my_best_friends.txt" ) .unwrap ();

if let Err (e) = writeln! (& mut file, "name: {}" , info.name) {

return Err (e)

}

if let Err (e) = writeln! (& mut file, "age: {}" , info.edad) {

return Err (e)

}

if let Err (e) = writeln! (& mut file, "grade: {}" , info.rgrado) {

return Err (e)

}

return Ok (());

}

With:

use std :: fs :: File;

use std :: io;

use std :: io :: prelude :: *;

struct info {

name: String ,

age: i32 ,

grade: i32 ,

}

fn escribir_info (info: & Info) -> io :: Result <()> {

let mut file = File :: create ( "my_best_friends.txt" ) .unwrap ();

try! ( writeln! (& mut file, "name: {}" , info.name));

try! ( writeln! (& mut file, "age: {}" , info.edad));

try! ( writeln! (& mut file, "grade: {}" , info.grad));

return Ok (());

}

Wrap an expression with try! will result in the successful unwrapped ( Ok ) value , unless the result is Err , in which case Err is returned early by the function that wraps the try.

It is important to mention the fact that you can only use try! from a function that returns a Result , which means that you can't use try! inside main () , because main () doesn't return anything.

Foreign / External Function Interface

Introduction

This guide will use the snappy compression / decompression library as an introduction to writing bindings to external code. Rust currently cannot call code in a C ++ library directly, but snappy includes a C interface (documented in snappy-ch ).

The following is a handy example of how to call a foreign function that will compile assuming snappy is installed:

# #! [feature (libc)]

extern crate libc;

use libc :: size_t;

# [link (name = "snappy")]

external

fn snappy_max_compressed_length (source_length: size_t) -> size_t;

}

fn main () {

let x = unsafe {snappy_max_compressed_length (100)};

println! ("maximum length of a 100 bytes compressed buffer: {}", x);

}

The extern block is a list of function signatures in a foreign library, in this case with the platform's C application binary interface (ABI). The # [link (...)] attribute is used to instruct the linker to link to the snappy library so that symbols can be resolved.

It is assumed that the interfaces to foreign functions are insecure, that is why the calls to them must be inside an unsafe {} block as a promise to the compiler that everything contained in it is really safe. Libraries in C sometimes expose interfaces that are not thread-safe, and almost any function that takes a pointer as an argument is not valid for all possible inputs because the pointer could be a hanging pointer, and flat pointers are left behind. outside of Rust's secure memory model.

When declaring the argument types of a foreign function, the Rust compiler cannot check whether the declaration is correct, so specifying it correctly is part of maintaining the correct binding at runtime.

The extern block can be extended to cover the full snappy API:

# #! [feature (libc)]

extern crate libc;

use libc :: {c_int, size_t};

# [link (name = "snappy")]

external

fn snappy_compress (input: * const u8,

input_length: size_t,

compressed: * mut u8,

compressed_length: * mut size_t) -> c_int;

fn snappy_uncompress (compressed: * const u8,

compressed_length: size_t,

uncompressed: * mut u8,

uncompressed_length: * mut size_t) -> c_int;

fn snappy_max_compressed_length (source_length: size_t) -> size_t;

fn snappy_uncompressed_length (compressed: * const u8,

compressed_length: size_t,

result: * mut size_t) -> c_int;

fn snappy_validate_compressed_buffer (compressed: * const u8,

compressed_length: size_t) -> c_int;

}

# fn main () {}

Creating a secure interface

The flat C interface needs to be wrapped in order to provide memory management security as well as the use of high level concepts such as vectors. A library can choose between exposing the high-level secure interface and hiding insecure internal details.

Wrapping the functions waiting for buffers involves using the slice :: raw module to manipulate Rust vectors as memory pointers. Rust vectors are guaranteed to be a contiguous memory block. Length is the number of elements currently contained, and capacity is the total size of allocated memory. The length is less than or equal to the capacity.

# #! [feature (libc)]

# extern crate libc;

# use libc :: {c_int, size_t};

# unsafe fn snappy_validate_compressed_buffer (_: * const u8 , _: size_t) -> c_int { 0 }

# fn main () {}

pub fn validate_buffer_compressed (src: & [ u8 ]) -> bool {

unsafe {

snappy_validate_compressed_buffer (src.as_ptr (), src.len () as size_t) == 0

}

}

The compressed_buffer_buffer wrapper function makes use of an unsafe block , but makes sure that calling it is safe for all inputs by excluding the unsafe from the function signature.

The snappy_compress and snappy_uncompress functions are more complex, because a buffer has to be assigned to maintain the output.

The snappy_max_compressed_length function can be used to assign a vector with the maximum capacity required to store the compressed output. The vector can then be passed to the snappy_compress function as an output parameter. An output parameter is also passed to get the actual length after compression to assign the length.

# #! [feature (libc)]

# extern crate libc;

# use libc :: {size_t, c_int};

# unsafe fn snappy_compress (a: * const u8 , b: size_t, c: * mut u8 ,

# d: * mut size_t) -> c_int { 0 }

# unsafe fn snappy_max_compressed_length (a: size_t) -> size_t {a}

# fn main () {}

pub fn compress (orig: & [ u8 ]) -> Vec < u8 > {

unsafe {

let long_orig = orig.len () as size_t;

let porig = orig.as_ptr ();

let mut long_dest = snappy_max_compressed_length (long_orig);

let mut dest = Vec :: with_capacity (long_dest as usize);

let pdest = dest.as_mut_ptr ();

snappy_compress (porig, long_orig, pdest, & mut long_dest);

dest.set_len (long_dest as usize);

dest

}

}

The decompression is similar, because snappy stores the decompressed length as part of the compression format and snappy_uncompressed_length will get the exact size of the required buffer.

# #! [feature (libc)]

# extern crate libc;

# use libc :: {size_t, c_int};

# unsafe fn snappy_uncompress (compressed: * const u8 ,

# compressed_length: size_t,

# uncompressed: * mut u8 ,

# uncompressed_length: * mut size_t) -> c_int { 0 }

# unsafe fn snappy_uncompressed_length (compressed: * const u8 ,

# compressed_length: size_t,

# result: * mut size_t) -> c_int { 0 }

# fn main () {}

pub fn unzip (orig: & [ u8 ]) -> Option < Vec < u8 >> {

unsafe {

let long_orig = orig.len () as size_t;

let porig = orig.as_ptr ();

let mut long_dest: size_t = 0 ;

snappy_uncompressed_length (porig, long_orig, & mut long_dest);

let mut dest = Vec :: with_capacity (long_dest as usize);

let pdest = dest.as_mut_ptr ();

if snappy_uncompress (porig, long_orig, pdest, & mut long_dest) == 0 {

dest.set_len (long_dest as usize);

Some (dest)

} else {

None // SNAPPY_INVALID_INPUT

}

}

}

Destroyers

External libraries usually transfer the membership of the resources to the calling code. When this occurs, we must use the Rust destroyers to provide security and the guarantee of the release of those resources (especially in the case of a panic).

Callbacks from C code to Rust functions

Some external libraries require the use of callbacks to report back their status or partial data to the caller. Functions defined in Rust can be passed to an external library. The requirement for this is that the callback function is marked as external and with the correct calling convention to make it possible to call from C code.

The callback function can then be sent through a register call to the library in C and its subsequent invocation from there.

A basic example is:

Rust code:

extern fn callback (a: i32) {

println! ("I have been called from C with the value {0}", a);

}

# [link (name = "extlib")]

external

fn register_callback (cb: extern fn (i32)) -> i32;

fn shoot_callback ();

}

fn main () {

unsafe {

register_callback (callback);

shoot_callback (); // Trigger the callback

}

}

Code C:

typedef void (* callback_rust) (int32_t) ;

callback_rust cb;

int32_t register_callback (callback_rust callback) {

cb = callback;

return 1 ;

}

void shoot_callback () {

cb ( 7 ); // Call callback (7) in Rust

}

In this example Rust's main () will call trigger_callback () in C, which in turn will callbackback () in Rust.

Aiming callbacks at Rust objects

The example above demonstrated how a global function can be called from C code. If I clutch sometimes I want the callback to point to a special Rust object. This could be the object that represents the wrapper for the respective object in C.

All of this can be accomplished by passing a flat pointer to the C library. The C library can then include the pointer to the Rust object in the notification. This will allow the callback to insecurely access the referenced Rust object.

Rust code:

# [repr (C)]

struct RustObject {

a: i32 ,

// other members

}

extern "C" fn callback (target: mut ObjectRust, to: i32) {

println! ("I have been called from C with the value {0}", a);

unsafe {

// Update the value in RustObject with the value received from the callback

(* target) .a = a;

}

}

# [link (name = "extlib")]

external

fn register_callback (target: mut ObjectRust,

cb: extern fn (* mut ObjectRust, i32)) -> i32;

fn shoot_callback ();

}

fn main () {

// Creating the object that will be referenced in the callback

let mut object_rust = Box :: new (ObjectRust {a: 5});

unsafe {

register_callback (& ​​mut * object_rust, callback);

shoot_callback ();

}

}

Code C:

typedef void (* callback_rust) ( void *, int32_t) ;

void * target_cb;

callback_rust cb;

int32_t register_callback ( void * callback_target, callback_rust callback) {

cb_target = callback_target;

cb = callback;

return 1 ;

}

void shoot_callback () {

cb (cb_target, 7 ); // I will call callback (& ​​RustObject, 7) in Rust

}

Asynchronous callbacks

In the examples above, callbacks are invoked as a direct reaction to a function call to the external library in C. Control over the current thread is changed from Rust to C for callback execution, but in the end the callback is executed in the same thread that called the function that triggered the callback.

Things get more complicated when the external library creates its own threads and invokes callbacks from them. In these cases access to Rust data structures within callbacks is especially insecure and appropriate synchronization mechanisms must be used. In addition to classic synchronization mechanisms such as mutexes, one possibility in Rust is the use of channels (in std :: sync :: mpsc ) to transfer data from the C thread that invokes the callback to a Rust thread.

If an asynchronous callback targets a special object in the Rust address space it is also absolutely necessary that no other callbacks be executed by the C library after the respective Rust object has been destroyed. This can be done by de-registering the callback in the object's destructor and designing the library in a way that guarantees that no callback will be executed after the de- registration.

Link

The link attribute in extern blocks provides the basic building block to instruct rustc how to link to native libraries. Today there are two forms of the link attribute:

# [link (name = "foo")]

# [link (name = "foo", kind = "bar")]

In both cases, foo is the name of the native library that we are linking to, and in the second case, bar is the type of native library that the compiler is linking to. There are currently three types of known native libraries:

Dynamics - # [link (name = "readline")]

Statics - # [link (name = "my_build_dependency", kind = "static")]

Frameworks - # [link (name = "CoreFoundation", kind = "framework")]

Note that frameworks are only available on OSX targets.

The different kind values are intended to differentiate how the native library participates in the link. From the binding perspective, the Rust compiler creates two types of artifacts: partial (rlib / staticlib) and final (dylib / binary). Dependencies on native libraries and frameworks are propagated to the final artifact boundary, while static dependencies are not propagated at all, because static libraries are integrated directly into the subsequent artifact.

A few examples of how this model can be used are:

A native build dependency: Sometimes some C / C ++ glue is required when writing a specific type of Rust code, but distributing the code in a library format is just a burden. In this case, the code will be archived in a libfoo.a and then the crate rust will declare a dependency via # [link (name = "foo", kind = "static")] .

Regardless of the type of output for the crate, the static native library will be included in the output, meaning that distribution of the native static library is not necessary.

A normal dynamic dependency: Common system libraries (such as readline ) are available on a large number of systems, and often a static copy of those libraries may not exist. When this dependency is included in a Rust crate, partial targets (like rlibs) will not link to the library, but when the rlib is included in a final target (like a binary), the library will be linked.

In OSX, frameworks behave with the same semantics as a dynamic library.

Unsafe blocks

Some operations, such as referencing flat pointers or calls to functions that have been marked as unsafe are only allowed within unsafe blocks. The unsafe blocks isolate the insecurity and are a promise to the compiler that the insecurity will not leak out of the block.

Unsafe functions, on the other hand, advertise insecurity to the outside world. An insecure function is written as follows:

unsafe fn kaboom (ptr: * const i32 ) -> i32 {*} ptr

This function can be invoked only from an unsafe block or another unsafe function .

Accessing external globals

Foreign APIs sometimes export a global variable that could do something like keep track of some global state. In order to access these variables, you must declare them in external blocks with the static keyword :

# #! [feature (libc)]

extern crate libc;

# [link (name = "readline")]

external

static rl_readline_version: libc :: c_int;

}

fn main () {

println! ("You have version {} of readline.",

rl_readline_version as i32);

}

Alternatively, you may need to alter the global state provided by a foreign interface. To do this, statics can be declared with mut in order to mutate them.

# #! [feature (libc)]

extern crate libc;

use std :: ffi :: CString;

use std :: ptr;

# [link (name = "readline")]

external

static mut rl_prompt: * const libc :: c_char;

}

fn main () {

let prompt = CString :: new ("[my-awesome-shell] $"). unwrap ();

unsafe {

rl_prompt = prompt.as_ptr ();

println! ("{:?}", rl_prompt);

rl_prompt = ptr :: null ();

}

}

Note that all interaction with a static mut is insecure, both read and write. Dealing with a mutable global state requires great care.

Foreign calling conventions

Most foreign code exposes an ABI C, and Rust defaults to the platform calling convention when calling external functions. Some foreign functions, notably the Windows API, use another calling convention. Rust provides a way to inform the compiler about which convention should be used:

# #! [feature (libc)]

extern crate libc;

# [cfg (all (target_os = "win32", target_arch = "x86"))]

# [link (name = "kernel32")]

# [allow (non_snake_case)]

extern "stdcall" {

fn SetEnvironmentVariableA (n: * const u8 , v: * const u8 ) -> libc :: c_int;

}

# fn main () {}

This applies to the entire extern block . The list of supported ABI constraints are:

stdcall

aapcs

cdecl

fastcall

Rust

rust-intrinsic

system

C

win64

Most abys are self-explanatory, but the abi system may seem a little weird. This constraint selects whatever the appropriate ABI is to interoperate with the target libraries. For example, in win32 with an x86 architecture, it means that the abi used will be stdcall . In x86_64, however, Windows uses the convention called C , then used C . This translates to that in our previous example, we could have used extern "system" {...} to define a block for all Windows systems, not just x86.

Interoperability with external code

Rust ensures that the distribution of a struct is compatible with the representation of the platform in C only if the attribute # [repr (C)] is applied. # [repr (C, packed)] can be used to distribute members without padding. # [repr (C)] can also be applied to an enum.

Rust boxes ( Box <T> ) use non-nullable pointers as handles that point to the contained object. However, they should not be created manually because they are handled by internal dispatchers. References can be safely assumed as direct non-nullable pointers to the type. However, breaking the borrowing check or the mutability rules is not guaranteed to be safe, which is why the use of flat pointers ( * ) is preferred if necessary because the compiler cannot assume many things about from them.

Vectors and character strings share the same in-memory layout, and utilities for interacting with C APIs are available in the vec and str modules . However, character strings are not terminated at \ 0 . If you need a character string ending in NUL to interact with C, then you must make use of the CString type in the std :: ffi module .

The standard library includes type aliases and function definitions for the C standard library in the libc module , and Rust defaults to libc and libm .

The "Null Pointer Optimization"

Certain types are defined not to be null . This includes references ( & T , & mut T ), boxes ( Box <T> ), and function pointers ( extern "abi" fn () ). When you interface with C, pointers that can be null are sometimes used. As a special case, a generic enum containing exactly two variants, of which one does not contain data and the other contains a single field, is eligible for "null pointer optimization". When said enum is instantiated with one of the non-nullable types, it is represented as a single pointer, and the variant without data is represented as the null pointer. So Option <extern "C" fn (c_int) -> c_int> is how one represents a nullable function pointer using ABI of C.

Calling coding Rust from C

You may want to compile Rust code so that it can be called from C. This is easy, but it requires certain things:

# [no_mangle]

pub extern fn hello_rust () -> * const u8 {

"Hello world! \ 0" .as_ptr ()

}

# fn main () {}

The extern makes this function adhere to the C calling convention, as discussed in " Foreign calling conventions ". The no_mangle attribute disables Rust's name mangling, making it easier to bind.

FFI and panics

It is important to be aware of panic! os when working with FFI. A panic! at the limit of FFI is undefined behavior. If you are writing code that can panic, you must run it in another thread, so the panic will not spread to C:

use std :: thread;

# [no_mangle]

pub extern fn oh_no () -> i32 {

let h = thread :: spawn (|| {

panic! ( "Oops!" );

});

match h.join () {

Ok (_) => 1 ,

Err (_) => 0 ,

}

}

# fn main () {}

Borrow and AsRef

Lost traits Borro w and AsRe f are very similar, but different. Here is a brief overview of what these traits mean:

Borrow

The Borrow trait is used when we are writing a data structure, and we want to use a owned type or a borrowed type as a synonym for some reason.

For example, HashMa p has a get method that uses Borrow :

fn get (& self, k: & Q) -> Option <& V>

where K: Borrow,

Q: Hash + Eq

This signature is complicated. Parameter K is what we are interested in now. It refers to a parameter of the HashMap itself:

struct HashMap {

Parameter K is the type of the key that the HashMap uses . When looking at the get () signature again , we can only use get () when the key implements Borrow <Q> . In this way, we can create a HashMap that uses String keys but when searching for use & str s:

use std :: collections :: HashMap;

let mut map = HashMap :: new ();

map.insert ( "Foo" .to_string (), 42 );

assert_eq! (map.get ( "Foo" ), Some (& 42 ));

This is because the standard library has a Borrow <str> for String impl .

For most types, when you want to take a type either owned or borrowed, an & T is enough. But one area in which Borrow is effective is when there is more than one type of borrowed security. This is especially true of references and slices: you can both have a & T one & mut T . If we want to accept both types, Borrow is what we need:

use std :: borrow :: Borrow;

use std :: fmt :: Display;

fn foo <T: Borrow < i32 > + Display> (a: T) {

println! ( "a is a borrowed: {}" , a);

}

let mut i = 5 ;

foo (& i);

foo (& mut i);

This will print out a is a borrowed: 5 twice.

AsRef

The AsRef trait is a conversion trait. It is used to convert some value to a reference in generic code. Like this:

let s = "Hello" .to_string ();

fn foo <T: AsRef < str >> (s: T) {

let slice = s.as_ref ();

}

Which one should you use?

We can see how they are both kind of the same: they both deal with owned and borrowed versions of some sort. However, they are different.

Choose Borrow when you want to abstract over different types of borrowing, or when you are building a data structure that treats owned and borrowed values ​​in equivalent ways, such as hashing and comparison.

Use AsRef when you want to convert something directly into a reference, and you're writing generic code.

Distribution channels

The Rust project uses a concept called 'distribution channels' to manage releases.

It is important to understand this process so that you can decide which version of Rust your project should use.

Overview

There are three channels for Rust releases:

Nocturnal (nightly)

Beta

Stable

Nightly releases are created once a day. Every six weeks, the latest nightly release is promoted to 'Beta'. At this point, you will only receive patches that fix serious bugs. Six weeks later the beta is promoted to 'Stable', and it becomes the new 1.x release .

This process occurs in parallel. Every six weeks, on the same day the nocturn goes up to beta, the beta is promoted to stable. When 1.x is released, at the same time 1. (x + 1) -beta is released, and the night becomes the first version of 1. (x + 2) -nightly .

Choosing a version

Generally speaking, unless you have a specific reason, you should use the stable distribution channel. Those releases are intended for a general audience.

However, depending on your interest in Rust, you could choose the night. The balance is as follows: on the night channel, you can make use of new Rust features. However, unstable features are subject to change, which is why any nightclub has the ability to break your code. If you use the stable release, you don't have access to experimental features, but the next release of Rust won't cause significant problems because of changes.

Helping the ecosystem through CI

What about beta? We recommend that all Rust users using the stable distribution channel also test their beta channel continuous integration systems. This will help warn the team in the event of an accidental regression.

Additionally, testing against nightly can detect regressions much earlier, which is why if you do not mind having a third build, we appreciate that you test against all channels.

Chapter IV

Syntax and Semantics

This section breaks Rust into small pieces, one for each concept.

If you want to learn Rust from the bottom up, reading this section in order is a very good way to do it.

These sections form a reference for each concept, if you are reading another tutorial and you find something confusing, you can find it explained somewhere in this section.

Variable Links

Virtually any non-'Hello World 'program uses variable links . These links to variables look like this:

fn main () {

let x = 5 ;

}

Placing fn main () { in each example is a bit tedious, so in the future we will skip it. If you are following step by step, be sure to edit your main () function , instead of leaving it out. Otherwise you will get an error.

In many languages, this is called a variable , but Rust's variable bindings have a few tricks up their sleeve. For example the left side of a let expression is a ' pattern ', not a simple variable name. This means that we can do things like:

let (x, y) = ( 1 , 2 );

After this expression is evaluated, x will be one, and y will be two. The patterns are really powerful, and they have their own section in the book. We don't need those facilities for now, let's just keep this in our minds as we go.

Rust is a statically typed language, which means that we specify our types in advance, and these are checked at compile time. So why does our first example compile? Well, Rust has this thing called 'type inference'. If you can determine the type of something, Rust doesn't require you to type the type.

We can add the type if we want. The types come after a colon ( :) :

let x: i32 = 5 ;

If I asked the rest of the class to read this out loud, you would say " x is a link to type i32 and the value five ."

In this case we decided to represent x as a 32-bit signed integer. Rust has many different types of primitive integers. These start with i for signed integers and u for unsigned integers. The possible sizes for integers are 8, 16, 32, and 64 bits.

In future examples, we could annotate the type in a comment. The example would look like this:

fn main () {

let x = 5 ; // x: i32

}

Notice the similarities between the annotation and the syntax you use with let . Including this kind of comment is not idiomatic in Rust, but we will include it occasionally to help you understand what types Rust infers.

By default, links are immutable . This code will not compile:

let x = 5;

x = 10;

It will result in the following error:

error: re-assignment of immutable variable `x`

x = 10;

^ ~~~~~~

If you want a variable link to be mutable, you can use mut :

let mut x = 5 ; // mut x: i32

x = 10 ;

There is no single reason why variable bindings are immutable by default, but we can think about it through one of Rust's main focuses: security. If you forget to say mut . the compiler will notice this, and let you know that you have mutated something you had no intention of mutating. If the links to variables were mutable by default, the compiler would not be able to tell you this. If you had the intention to mutate something, then the solution is very easy: add mut .

There are other good reasons to avoid mutable state whenever possible, but they are outside the scope of this guide. In general, you can often avoid explicit mutation, and this is preferable in Rust. That said, sometimes, mutation is just what you need, which is why it is not prohibited.

Let's go back to links to variables. Variable links in Rust have one more aspect that differs from other languages: initialization to a value is required before you can use them.

Let's put this to the test. Change your src / main.rs file so it looks like this:

fn main () {

let x: i32 ;

println! ( "Hello, world!" );

}

You can use cargo build on the command line to compile it. You will get a warning but it will still print "Hello world!":

Compiling hello_world v0.0.1 (file: /// home / you / projects / hello_world)

src / main.rs: 2: 9: 2:10 warning: unused variable: `x`, # [warn (unused_variable)]

on by default

src / main.rs: 2 let x: i32;

^

Rust warns us that we never make use of the variable link, but because we never use it, there is no danger, no fault. However, things change if we actually try to use x . Let's do that. Change your program to look like this:

fn main () {

let x: i32;

println! ("The value of x is: {}", x);

}

Try to compile it. You will get an error:

$ build charge

Compiling hello_world v0. 0.1 (file: /// home / you / projects / hello_world)

src / main.rs: 4 : 39 : 4 : 40 error: use of possibly uninitialized variable: `x`

src / main.rs: 4      println! ( "The value of x is: {}" , x) ;

^

note: in expansion of format_args!

<std macros>: 2 : 23 : 2 : 77 note: expansion site

<std macros>: 1 : 1 : 3 : 2 note: in expansion of println!

src / main.rs: 4 : 5 : 4 : 42 note: expansion site

error: aborting due to previous error

Could not compile `hello_world`.

Rust will not allow you to use a value that has not been previously initialized. Let's talk about what we've added to println below! .

If you include a key pair ( {} , some people call them mustaches / mustaches ...) in the string to print, Rust will interpret it as a request to interpolate some kind of value. String interpolation is a term in computer science that means "put this inside the string." We add a comma, and then x , to indicate that we want this to be the interpolated value. The comma is used to separate the arguments that we pass to the functions and the macros, in the case of passing more than one.

When you use the curly braces, Rust will try to display the value in a way that makes sense after checking its type. If we want to specify a more detailed format, there are a wide number of options available . For now, we will stick to the default behavior: integers are not very difficult to print.

Features

Every Rust program has at least one function, the main function :

fn main () {

}

This is the simplest possible function declaration. As we mentioned earlier, fn says 'this is a function', followed by the name, parentheses because this function does not receive any arguments, and then braces to indicate the body of the function. Here's a function called foo :

fn foo () {

}

So what about receiving arguments? Here is a function that prints a number:

fn print_number (x: i32 ) {

println! ( "x is: {}" , x);

}

Here is a complete program that makes use of print_number :

fn main () {

print_number ( 5 );

}

fn print_number (x: i32 ) {

println! ( "x is: {}" , x);

}

As you will see, function arguments work very similarly to let statements : you add a type to the argument name, after a colon.

Here is a complete program that adds two numbers and then prints them:

fn main () {

print_sum ( 5 , 6 );

}

fn print_sum (x: i32 , y: i32 ) {

println! ( "the sum is: {}" , x + y);

}

The arguments are separated by a comma, in both cases, when you call the function as well as when you declare it.

Unlike let , you must declare the types of the arguments. The following will not compile:

fn print_sum (x, y) {

println! ("sum is: {}", x + y);

}

You would get the following error:

expected one of `!`, `:`, or `@`, found `)`

fn print_sum (x, y) {

This is a deliberate design decision. Although full program inference is possible, languages ​​that own it, like Haskell, often suggest that documenting your types explicitly is good practice. We agree that forcing functions to declare types while allowing inference within the function is a wonderful compromise between complete inference and the absence of inference.

What about returning a value? Here is a function that adds one to an integer:

fn sum_one (x: i32 ) -> i32 {

x + 1

}

The functions in Rust return exactly one value, and the type is declared after an 'arrow', which is a hyphen ( - ) followed by a greater-than sign ( > ). The last line of a function determines what it returns. You will notice the absence of a semicolon here. Add it:

fn sum_one (x: i32) -> i32 {

x + 1;

}

We would get an error:

error: not all control paths return a value

fn sum_one (x: i32) -> i32 {

x + 1;

}

help: consider removing this semicolon:

x + 1 ;

^

The above reveals two interesting things about Rust: it is an expression-based language, and the semicolons are different from other languages ​​based on 'braces and semicolons'. These two things are related.

Expressions vs. Sentences

Rust in a language primarily based on expressions. There are only two types of statements, everything else is an expression.

So what is the difference? Expressions return a value, and statements do not. This is why we end up with the error 'not all control paths return a value' here: the statement x + 1; does not return a value. There are two types of statements in Rust: 'statement statements' and 'expression statements'. Everything else is an expression. Let's talk about declaration statements first.

In some languages, variable links can be written as expressions, not just statements. As in Ruby:

x = y = 5

In Rust, however, using let to enter a variable link is not an expression. The following will produce a compile-time error:

let x = (let y = 5); // expected identifier, found keyword `let`

The compiler tells us that it was waiting for the start of an expression, and a let can be just a statement, not an expression.

Note that assigning a variable that has been previously assigned (eg y = 5 ) is an expression, yet its value is not particularly useful. Unlike other languages ​​in which an assignment is evaluated to the assigned value (ex 5 in the previous example), in Rust the value of an assignment is an empty tuple () because the assigned value can have a single owner and any Another return value would be surprising:

let mut y = 5 ;

let x = (y = 6 );  // x has the value `()`, not `6`

The second type of statement in Rust is the expression statement . Its purpose is to convert any expression into a sentence. In practical terms, Rust's grammar expects a sentence to be followed by more sentences. This means that you can use semicolons to separate one expression from another. This causes Rust to look very similar to those languages ​​in which a semicolon is required at the end of each line, in fact, you will see semicolons at the end of almost every Rust line you will see.

So what is the exception that makes us say "almost"? You've already seen it in the code above:

fn sum_one (x: i32 ) -> i32 {

x + 1

}

Our function claims to return an i32 , but with a semicolon, it would return () . Rust determines that this is probably not what we want, and suggests removing the semicolon in the error we saw earlier.

Early returns

But what about early returns? Rust provides a reserved word for it, return :

fn foo (x: i32 ) -> i32 {

return x;

// we will never reach this code!

x + 1

}

Using a return as the last line of a function is valid, but it is considered poor style:

fn foo (x: i32 ) -> i32 {

return x + 1 ;

}

The previous definition without the return may look a bit strange if you've never worked with an expression-based language, but this one becomes intuitive over time.

Divergent functions

Rust has a special syntax for 'divergent functions', which are non-returning functions:

fn diverges () ->! {

panic! ( "This function never returns!" );

}

panic! it is a macro, similar to println! () that we have already seen. Unlike println! () , Panic! () Causes the current thread to end abruptly displaying the provided message.

Because this function will cause an abrupt exit, it will never return, that is why it has the type ' ! ', which reads' diverges'. A divergent function can be used as any type:

# fn diverges () ->! {

# panic! ("This function never returns!");

#}

let x: i32 = diverge ();

let x: String = diverge ();

Function pointers

We can also create links to variables that point to functions:

let f: fn ( i32 ) -> i32 ;

f is a binding to variable that points to a function that takes an i32 as its argument and returns an i32 . For example:

fn mas_uno (i: i32 ) -> i32 {

i + 1

}

// no type inference

let f: fn ( i32 ) -> i32 = mas_uno;

// type inference

let f = more_one;

We can then use f to call the function:

# fn mas_uno (i: i32 ) -> i32 {i + 1 }

# let f = more_one;

let six = f ( 5 );

Primitive Types

Rust has a set of types that are considered 'primitive'. This means that they are integrated into the language. Rust is structured in such a way that the standard library also provides a number of useful types based on primitives, but these are the most primitive.

Boolean

Rust has a built-in Boolean type, called bool . It has two possible values true and false :

let x = true ;

let y: bool = false ;

A common use of booleans is in if conditionals .

You can find more documentation for the bool eanos in the documentation of the standard library (english).

char

The char type represents a single Unicode scalar value. You can create char s with single quotes: ( ' )

let x = 'x' ;

let dos_corazones = ' �� ';

Unlike other languages, this means that char in Rust is not a single byte, but four.

You can find more documentation for the char s in the standard library documentation .

Numerical types

Rust has a variety of number types in a few categories: signed and unsigned, fixed and variable, floating point, and integers.

These types consist of two parts: the category, and the size. For example, u16 is an unsigned type with a size of sixteen bits. More bits allow you to store larger numbers.

If a number literal does not have anything that causes its type to be inferred, default types are used:

let x = 42 ; // x has type i32

let y = 1.0 ; // and it has type f64

Here is a list of the different numeric types, with links to their documentation in the standard library (English):

i8

i16

i32

i64

u8

u16

u32

u64

isize

usize

f32

f64

Let's look at each of the different categories.

Signed and unsigned

Integer types come in two varieties: signed and unsigned. To understand the difference, consider a number that is four bits in size. A four-bit signed number would allow you to store numbers from -8 to +7 . Signed numbers use the "two's complement representation". A number of four unsigned bits, because you don't need to save negative values, can store values ​​from 0 to +15 .

Unsigned types use a u for their category, and signed types use i . The i is for integer (integer). So u8 is an eight-bit unsigned number, and an i8 is an eight-bit signed number .

Fixed size types

Fixed-size types have a specific number of bits in their representation. Valid sizes are 8 , 16 , 32 , and 64 . So u32 is a 32-bit unsigned integer, and i64 is a 64-bit signed integer.

Variable size types

Rust also provides types for which the size depends on the size of the pointer on the underlying machine. These types have the category 'size', and come in both signed and unsigned variants. This results in two types isize and usize .

Floating point types

Rust also has two types of floating point: f32 and f64 . These correspond to the IEEE-754 single and double precision numbers.

Arrangements

Like many programming languages, Rust has list types to represent a sequence of things. The most basic is the arrangement , a list of elements of the same type and of fixed size. By default the arrangements are immutable.

let a = [ 1 , 2 , 3 ]; // a: [i32; 3]

let mut m = [ 1 , 2 , 3 ]; // m: [i32; 3]

The arrays have the type [T; N] . We will talk about the T notation in the generic section . The N is a constant at compile time, for the length of the arrangement.

There is a shortcut for initializing each of the array elements to the same value. In this example, each element of a will be initialized to 0 :

let a = [ 0 ; 20 ]; // a: [i32; twenty]

You can get the number of elements of the array a with a.len ()

let a = [ 1 , 2 , 3 ];

println! ( "a has {} elements" , a.len ());

You can access a particular array element with subscript notation :

let names = [ "Graydon" , "Brian" , "Niko" ]; // names: [& str; 3]

println! ( "The second name is: {}" , names [ 1 ]);

Subindices start at zero, as in most programming languages, so the first name is names [0] and the second name is names [1] . The example above prints: The middle name is: Brian . If you try to use a subscript that is not in the array, you will get an error: checking the limits on access to the array is done at runtime. Wandering access like that is the source of many bugs in system programming languages.

You can find more documentation for s- arrays in the standard library documentation .

Slices

A slice is a reference to (or a "view" within) another data structure. Slices are useful for allowing secure and efficient access to a portion of an array without involving copying. For example, you might want to refer to a single line in a file that has been previously read from memory. By nature, a slice is not created directly, they are created for a variable that already exists. Slices have a length, can be mutable or immutable, and in many ways behave like arrays:

let a = [ 0 , 1 , 2 , 3 , 4 ];

let middle = & a [ 1 .. 4 ]; // A slice of a: only elements 1, 2, and 3

let complete = & a [..]; // A slice containing all the elements of a.

Slices have the & [T] type . We will talk about T when we cover generics .

You can find more documentation for slices in the documentation of the standard library (English)

str

The str of Rust is the most primitive type of string. As a sizeless type , and is not very useful in itself, but becomes very useful when put behind a reference, like & st r . That is why we will leave it up to here.

You can find more documentation for str in the standard library documentation (English)

Tuples

A tuple is an ordered list of fixed size. Like this:

let x = ( 1 , "hello" );

The parentheses and commas form this tuple of length two. Here is the same code but with type annotations:

let x: ( i32 , & str ) = ( 1 , "hello" );

As you can see, the type of a tuple is just like the tuple, but with each position having the type instead of the value. Careful readers will also notice that tuples are heterogeneous: we have an i32 ya & str in this tuple. In system programming languages, character strings are slightly more complex than in other languages. For now just read & str as a character string slice . We will learn more shortly.

You can assign one tuple to another, if they have the same contained types and the same arity . Tuples have the same arity when they are the same size.

let mut x = ( 1 , 2 ); // x: (i32, i32)

let y = ( 2 , 3 ); // y: (i32, i32)

x = y;

You can access the fields of a tuple through an unstructured let . Here is an example:

let (x, y, z) = ( 1 , 2 , 3 );

println! ( "x is {}" , x);

Remember earlier when I said that the left side of a let statement was more powerful than simply assigning a binding? Here we are. We can place a pattern on the left side of a let , and if it matches the right side, we can assign multiple bindings to a variable at once. In this case, the let "unstructures" or "part" the tuple, and assigns the parts to the three bindings to variable.

This pattern is very powerful, and we will see it repeated frequently in the future.

You can remove ambiguity between a single-element tuple and a value enclosed in parentheses using a comma:

( 0 ,); // single element tuple

( 0 ); // zero enclosed in parentheses

Indexed in tuples

You can also access the fields of a tuple with the indexing syntax:

let tuple = ( 1 , 2 , 3 );

let x = tuple. 0 ;

let y = tuple. 1 ;

let z = tuple. 2 ;

println! ( "x is {}" , x);

Like array indexing, it starts at zero, but unlike array indexing, they are used . , instead of [] s.

You can find more documentation for tuples in the standard library documentation (English)

Features

Functions also have a type! They look like this:

fn foo (x: i32 ) -> i32 {x}

let x: fn ( i32 ) -> i32 = foo;

In this case, x is a 'pointer' to a function that receives an i32 and returns an i32 .

Comments

Now that we've seen some features, it's a good idea to learn about the comments. Comments are notes you leave to other programmers in order to explain some aspect of your code. The compiler mostly ignores them.

Rust has two types of comments that should interest you: line comments and documentation comments

// Line comments are anything after '//' and extend to the end of the line

let x = 5; // this is also a line comment

// If you have a long explanation about something, you can put comments

// line some together. Put a space between your // and your comment with the

// in order to make them more readable.

The other type of comment is a documentation comment (or doc comment). The doc comments use /// instead of // , and support Markdown notation inside:

/// Add one to the number provided

///

/// # Examples

///

///

/// let five = 5; /// /// assert_eq! (6, sum_one (5)); /// # fn sum_one (x: i32) -> i32 {/// # x + 1 /// #} /// `` `fn sum_one (x: i32) -> i32 {x + 1}

There is another style of comments, `//!`, With the purpose of commenting the container items (eg crates, modules or functions), instead of the items that follow them. They are commonly used in the root of a crate (lib.rb) or in the root of a module (mod.rs):

//! # The Rust Standard Library //! //! The Rust Standard Library provides the functionality //! runtime essential for building software //! Rust portable. `` ''

When writing doc comments, providing some usage examples is very, very useful. You will notice that we have made use of a macro: assert_eq! . It compares two values, and panics! or if these are not the same. It is very useful in documentation. There is also another macro, assert! , which panics! or if the supplied value is false .

You can use the rustdoc tool to generate HTML documentation from those comments, as well as run the code in the examples as tests!

if

Rust's approach to if is not particularly complex, but is more similar to the if you will find in languages ​​with dynamic typing than in traditional system languages. Let's talk a little about this, to make sure you understand all the nuances.

if is a specific form of a more general concept, the 'branch'. The name comes from a branch in a tree: it is a decision point, in which depending on an option, multiple paths can be taken.

In the case of if , there is a single choice that leads to two paths:

let x = 5;

if x == 5 {

println! ("x is five!");

}

Had the value of x changed to something different, this line would not have been printed. To be more specific, if the expression after the if is evaluated to true , then the code block is executed. If false , that block is not invoked.

If you want something to happen in the case of false , you must use an else :

let x = 5;

if x == 5 {

println! ("x is five!");

} else {

println! ("x is not five :(");

}

If there is more than one case, use an else if :

let x = 5;

if x == 5 {

println! ("x is five!");

} else if x == 6 {

println! ("x is six!");

} else {

println! ("x is neither five nor six :(");

}

This is all very standard. However, you can also do this:

let x = 5;

let y = if x == 5 {

10

} else {

fifteen

}; // y: i32

Which we can (and probably should) write like this:

let x = 5;

let y = if x == 5 {10} else {15}; // y: i32

This works because if is an expression. The value of the expression is the value of the last expression in the block that has been selected. An if without an else always results in () as a value.

Cycles

Rust currently provides three approaches to performing iterative activity. loop , while and for . Each of these approaches has its own uses.

loop

The infinite loop cycle is the simplest cycle available in Rust. Through the use of the loop keyword , Rust provides a way to iterate indefinitely until some termination statement is reached. The loop infinite Rust looks like this :

loop {

println! ("Itera forever!");

}

while

Rust also has a while loop . It looks like this:

let mut x = 5 ; // mut x: i32

let mut completed = false ; // mut completed: bool

while ! completed {

x + = x - 3 ;

println! ( "{}" , x);

if x% 5 == 0 {

completed = true ;

}

}

While loops are the right choice when you are not sure how many times you need to iterate.

If you need an infinite loop, you might be tempted to write something like this:

while true {

However, loop is by far the best one for this case:

loop {

Rust's control flow analysis treats this construction differently than a while true , because we know that we will iterate forever. In general, the more information we provide to the compiler, the compiler could perform better in relation to security and code generation, that is why you should always prefer loop when you plan to iterate indefinitely.

for

The for loop is used to iterate a particular number of times. Cycles for Rust, however, work differently from other programming languages way systems. The for Rust does not look like this cycle for a "C style"

for (x = 0 ; x < 10 ; x ++) {

printf ( "% d \ n" , x);

}

Instead, Rust's for loop looks like this:

for x in 0 .. 10 {

println! ( "{}" , x); // x: i32

}

In slightly more abstract terms:

for var in expression {

code

}

The expression is an iterator . The iterator returns a series of elements. Each element is an iteration of the cycle. That value is in turn assigned to the name var , which is valid in the body of the cycle. Once the cycle has finished, the next value is obtained from the iterator, and is iterated once more. When there are no more values, the for loop ends.

In our example, 0..10 is an expression that takes a start position and an end position, and returns an iterator above those values. The upper limit is exclusive, so our loop will print from 0 to 9 , not 10 .

Ruth does not possess the C-style for loop , by the way. Manually controlling each element of the cycle is complicated and error prone, even for experienced C programmers.

List yourself

When you need to keep track of how many times you've iterated, you can use the .enumerate () function .

In ranges:

for (i, j) in ( 5 .. 10 ) .enumerate () {

println! ( "i = {} and j = {}" , i, j);

}

Departure:

i = 0 and j = 5

i = 1 and j = 6

i = 2 and j = 7

i = 3 and j = 8

i = 4 and j = 9

Don't forget to put the parentheses around the range.

In iterators:

# let lines = "hello \ nworld" .lines ();

for (line_number, line) in lineas.enumerate () {

println! ( "{}: {}" , line_number, line);

}

Outputs:

0: Contents of line one

1: Contents of line two

2: Contents of line three

3: Contents of line four

Ending the iteration early

Let's take a look at that while loop we saw earlier:

let mut x = 5 ;

let mut completed = false ;

while ! completed {

x + = x - 3 ;

println! ( "{}" , x);

if x% 5 == 0 {

completed = true ;

}

}

We need to maintain a binding variable mut , completed , to know when we should get out of the cycle. Rust has two keywords to help us modify the iteration process: break and continue .

In this case, we can write the cycle in a better way with break :

let mut x = 5 ;

loop {

x + = x - 3 ;

println! ( "{}" , x);

if x% 5 == 0 { break ; }

}

We now loop indefinitely and use break to break the loop early. Using an explicit return also works well for early cycle termination.

continue is similar, but instead of ending the loop, it makes us go to the next iteration. The following will print the odd numbers:

for x in 0 .. 10 {

if x% 2 == 0 { continue ; }

println! ( "{}" , x);

}

Loop tags

You might also encounter situations where you have nested loops and need to specify which loop your break or continue statements belong to . As in most languages, by default a break or continue applies to the innermost loop. In the event that you want to apply a break or continue to any of the external cycles, you can use labels to specify to which cycle the break or continue statement applies . The following will only print when both x and y are odd:

'exterior : for x in 0 .. 10 {

'interior : for and in 0 .. 10 {

if x% 2 == 0 { continue 'exterior ; } // continue the loop above x

if and% 2 == 0 { continue 'inside ; } // continue the loop above and

println! ( "x: {}, y: {}" , x, y);

}

}

Belonging

This guide is one of three introducing the Rust membership system. This is one of the most unique and irresistible features of Rust, with which Rust developers should be familiar. It is through membership that Rust achieves his most important goal, security. There are a few different concepts, each with its own chapter:

membership, the one you currently read

loan , and its associated characteristic 'references'

life time , an advanced loan concept

These three chapters are related, in order. You will need all three to fully understand Rust's membership system.

Goal

Before going into detail, two important notes about the membership system.

Rust is focused on safety and speed. Rust achieves these goals through many 'zero cost abstractions', which means that at Rust, abstractions cost as little as possible to make them work. The membership system is a prime example of a zero cost abstraction. All the analysis that we will be talking about in this guide is carried out at compilation time . You do not pay any cost at runtime for any of these facilities.

However, this system has a certain cost: the learning curve. Many new Rust users experience something we call 'fighting with the borrow checker', a situation in which the Rust compiler refuses to compile a program which the author thinks is valid. This occurs frequently because the programmer's mental model of how membership works does not conform to the current rules implemented in Rust. You probably experience similar things in the beginning. However, there is good news: other experienced Rust developers report that once they work with membership system rules for a period of time, they struggle less and less with the loan checker.

With that in mind, let's learn about belonging.

Belonging

The Bindings variable holding property Rust: These 'have membership' what are associated. This means that when a binding to a variable goes out of scope, Rust releases the resources associated with it. For example:

fn foo () {

let v = vec! [ 1 , 2 , 3 ];

}

When v comes into scope, a new Vec <T > is created. In this case the vector also allocates some memory from the mound , for the three elements. When v goes out of scope at the end of foo () , Rust will clear everything related to the vector, including the memory allocated from the mound. This occurs deterministically, at the end of the scope.

Motion semantics

There is something more subtle here, Rust makes sure that there is only exactly one binding to any particular resource. For example, if we have a vector, we can assign it to another binding to variable:

let v = vec! [ 1 , 2 , 3 ];

let v2 = v;

But, if we try to use v afterwards, we get an error:

let v = vec! [1, 2, 3];

let v2 = v;

println! ("v [0] is: {}", v [0]);

The error looks like this:

error: use of moved value: `v` (use of moved value:` v`)

println! ("v [0] is: {}", v [0]);

^

Something similar happens if we define a function that takes membership, and we try to use something after having passed it as an argument:

fn take (v: Vec) {

// what happens here is not relevant

}

let v = vec! [1, 2, 3];

take (v);

println! ("v [0] is: {}", v [0]);

The same error: 'use of moved value'. When we transfer membership into something, we say that we have 'moved' the thing to which we are referring. You don't need any special annotation for this, which is what Rust does by default.

The details

The reason why we can't use a binding to variable after we have moved it is subtle, but important. When we write code like this:

let v = vec! [ 1 , 2 , 3 ];

let v2 = v;

The first line allocates memory for the vector object, v , and for the data it contains. The vector object is then stored in the stack stack and contains a pointer to the content ( [1, 2, 3] ) stored in the mound . When we move v to v2 , a copy of that pointer is created for v2 . All this means that there would be two pointers for the vector content in the mound. This violates Rust's safety guarantees, introducing a race condition. That is why Rust prohibits the use of v after the movement has been carried out.

Importantly, some optimizations might remove the copy of the bytes on the stack, depending on certain circumstances. So it may not be as inefficient as it initially looks.

Copy Types

We have established that when we transfer the membership to another binding to variable, we cannot use the original binding. However, there is a trait trait that changes this behavior, it is called Copy . We haven't discussed traits yet, but for now you can see them as an annotation made to a particular type that adds extra behavior. For example:

let v = 1 ;

let v2 = v;

println! ( "v is: {}" , v);

In this case, v is an i32 , which implements the Copy trait . This means that, just as in a move, when we assign v to v2 , a copy of the data is made. But, unlike what happens in a movement, we can use v afterwards. This is because an i32 doesn't have data pointers anywhere else, copying it means full copying.

All primitive types implement the Copy trait and their membership is not moved as one might assume, following the membership rules . As an example, the following two pieces of code only compile because the i32 and bool types implement the Copy trait .

fn main () {

let a = 5 ;

let _y = fold (a);

println! ( "{}" , a);

}

fn double (x: i32 ) -> i32 {

x * 2

}

fn main () {

let a = true ;

let _y = change_truth (a);

println! ( "{}" , a);

}

fn change_truth (x: bool ) -> bool {

! x

}

If we had types that did not implement the Copy trait , we would have got a compilation error for trying to use a moved value.

error: use of moved value: `a`

println! ("{}", a);

^

We will discuss how to make your own types Copy in the traits section

More than belonging

Of course, if we need to return the membership with each function that we write:

fn foo (v: Vec < i32 >) -> Vec < i32 > {

// do something with v

// returning membership

v

}

Things would get quite tedious. It gets even worse as we have more things that we want to belong to:

fn foo (v1: Vec < i32 >, v2: Vec < i32 >) -> ( Vec < i32 >, Vec < i32 >, i32 ) {

// do something with v1 and v2

// returning membership, as well as the result of our function

(v1, v2, 42 )

}

let v1 = vec! [ 1 , 2 , 3 ];

let v2 = vec! [ 1 , 2 , 3 ];

let (v1, v2, answer) = foo (v1, v2);

Ugh! The return type, the return line, and the function call become much more complicated.

Fortunately, Rust offers a facility, the loan, a facility that helps us solve this problem.

It is the topic of the next section!

References and Loan

This guide is one of three introducing the Rust membership system. This is one of the most unique and attractive features of Rust, with which Rust developers should be well acquainted. Membership is how Rust achieves its highest goal, memory management security. There are a few different concepts, each with its own chapter:

membership , the main concept

loan, the one you read now

life times , an advanced loan concept

These three chapters are related, and in order. You will need to read all three to fully understand the membership system.

Goal

Before going into detail, two important notes about the membership system.

Rust is focused on safety and speed. Rust achieves these goals through many 'zero cost abstractions', which means that at Rust, abstractions cost as little as possible to make them work. The membership system is a prime example of a zero cost abstraction. All the analysis that we will be talking about in this guide is carried out at compilation time . You do not pay any cost at runtime for any of these facilities.

However, this system has a certain cost: the learning curve. Many new Rust users experience something we call 'fighting with the borrow checker', a situation in which the Rust compiler refuses to compile a program which the author thinks is valid. This occurs frequently because the programmer's mental model of how membership works does not conform to the current rules implemented in Rust. You probably experience similar things in the beginning. However, there is good news: other experienced Rust developers report that once they work with membership system rules for a period of time, they struggle less and less with the loan checker.

With that in mind, let's learn about the loan.

Loan

At the end of the membership section , we had an ugly function that looked like this:

fn foo (v1: Vec < i32 >, v2: Vec < i32 >) -> ( Vec < i32 >, Vec < i32 >, i32 ) {

// do something with v1 and v2

// returning membership, as well as the result of our function

(v1, v2, 42 )

}

let v1 = vec! [ 1 , 2 , 3 ];

let v2 = vec! [ 1 , 2 , 3 ];

let (v1, v2, answer) = foo (v1, v2);

The above, however, is not idiomatic Rust, since it does not benefit from the advantages of the loan. Here is the first step:

fn foo (v1: & Vec < i32 >, v2: & Vec < i32 >) -> i32 {

// do something with v1 and v2

// returning the answer

42

}

let v1 = vec! [ 1 , 2 , 3 ];

let v2 = vec! [ 1 , 2 , 3 ];

let answer = foo (& v1, & v2);

// we can use v1 and v2 here

Instead of taking Vec <i32> s as arguments, we take a reference: & Vec <i32> . And instead of passing v1 and v2 directly, we pass & v1 and & v2 . We call the & T type a 'reference', and instead of taking ownership of the resource, it borrows it. A variable link that borrows something doesn't release the resource when it goes out of scope. This means that after the foo () call , we can again make use of the original variable bindings.

References are immutable, just like variable links. This translates to that within foo () , the vectors cannot be changed:

fn foo (v: & Vec) {

v.push (5);

}

let v = vec! [];

foo (& v);

fails with:

error: cannot borrow immutable borrowed content `* v` as mutable

v.push (5);

^

Inserting a value causes a mutation in the vector, and we are not allowed to do so.

references & mut

There is a second type of reference: & mut T . A 'mutable reference' that allows you to mutate the resource you are borrowing. For example:

let mut x = 5 ;

{

let y = & mut x;

* and + = 1 ;

}

println! ( "{}" , x);

The above will print 6 . We make y a mutable reference to x , then add one to whatever y points to. You will notice that x also had to be marked as mut , otherwise we would not have been able to take a mutable loan at immutable value.

You will also notice that we have added an asterisk ( * ) in front of y , making it * and , this is because y is a & mut reference . You will also need to use them to access the content of a reference.

Otherwise, & mut references are like references. There is a big difference between the two, and how they interact. You will have noticed that there is something that does not smell very good in the previous example, since we need that extra scope, with the { y } . If we remove them, we get an error:

error: cannot borrow `x` as immutable because it is also borrowed as mutable

println! ("{}", x);

^

note: previous borrow of `x` occurs here; the mutable borrow prevents

subsequent moves, borrows, or modification of `x` until the borrow ends

let y = & mut x;

^

note: previous borrow ends here

fn main () {

}

^

Apparently there are rules.

The rules

Here are the rules about the loan at Rust:

First, any loan must live in a realm no larger than that of the owner. Second, you can have one or the other of these types of loans, but not both at the same time:

one or more references ( & T ) to a resource,

exactly a mutable reference ( & mut T ).

You may notice that this is very similar, but not exactly the same, to the definition of a race condition:

There is a 'race condition' when two or more pointers access the same memory location at the same time, where at least one is writing, and the operations are not synchronized.

With the references, you can have as many as you want, since none of them are writing. If you are writing, and you need two or more pointers to the same memory, you can have only one & mut at a time. This is how Rust prevents race conditions at compile time: we will get errors if we break the rules.

With this in mind, let's consider our example again.

Thinking about scopes

Here is the code:

let mut x = 5;

let y = & mut x;

* and + = 1;

println! ("{}", x);

The above code generates the following error:

error: cannot borrow `x` as immutable because it is also borrowed as mutable

println! ("{}", x);

^

This is because we have violated the rules: we have a & mut T pointing to x , and consequently we are not allowed to create any & T s. One thing or another. The note points to how to think about this problem:

note: previous borrow ends here

fn main () {

}

^

In other words, the mutable loan is maintained throughout the rest of our example. What we want is for our mutable loan to end before we try to call println! and let's make an immutable loan. At Rust, the loan is associated with the area in which the loan is valid. Our areas look like this:

let mut x = 5;

let y = & mut x; // - + x loan & mut starts here

// |

* and + = 1; // |

// |

println! ("{}", x); // - + - attempt to borrow x here

// - + x loan & mut ends here

The scopes conflict: we cannot create an & x while y is in scope.

So when we add braces:

let mut x = 5 ;

{

let y = & mut x; // - + x loan & mut starts here

* and + = 1 ;        // |

}                   // - + ... and ends here

println! ( "{}" , x);  // <- attempt to borrow x here

No problem. Our mutable loan goes out of scope before we create an immutable loan. The scope is key to see how long the loan lasts.

Problems that the loan prevents

Why do we have these restrictive rules? Well, as we noted, these rules prevent race conditions. What kinds of problems do race conditions cause? Here a few.

Iterator Invalidation

An example is 'iterator override', which occurs when you try to mutate a collection while iterating over it. Rust's loan checker prevents this from happening:

let mut v = vec! [ 1 , 2 , 3 ];

for i in & v {

println! ( "{}" , i);

}

The above prints from one to three. As we iterate the vectors, we are only provided with references to their elements. v itself is immutably borrowed, which means we can't change it while iterating:

let mut v = vec! [1, 2, 3];

for i in & v {

println! ("{}", i);

v.push (34);

}

Here is the error:

error: cannot borrow `v` as mutable because it is also borrowed as immutable

v.push (34);

^

note: previous borrow of `v` occurs here; the immutable borrow prevents

subsequent moves or mutable borrows of `v` until the borrow ends

for i in & v {

^

note: previous borrow ends here

for i in & v {

println! (“{}”, i);

v.push (34);

}

^

We cannot modify v because it is borrowed by the cycle.

use after release

References should not live any longer than the resource they point to. Rust will check the scope of your references to make sure this is true.

If Rust did not verify this property, we could accidentally use an invalid reference. For example:

let y: & i32;

{

let x = 5;

y = & x;

}

println! ("{}", and);

We get the following error:

error: `x` does not live long enough

y = & x;

^

note: reference must be valid for the block suffix following statement 0 at

2:16 ...

let y: & i32;

{

let x = 5;

y = & x;

}

note: ... but borrowed value is only valid for the block suffix following

statement 0 at 4:18

let x = 5;

y = & x;

}

In other words, y is valid only for the area where x exists. As soon as x leaves, reference is invalid. That is why the error says that the loan, 'does not live long enough' ('does not live long enough') since it is not valid for the correct amount of time.

The same problem occurs when the reference is declared before the variable to which it refers. This is because the resources within the same scope are released in the opposite order to the order in which they were declared:

let y: & i32;

let x = 5;

y = & x;

println! ("{}", and);

We get this error:

error: `x` does not live long enough

y = & x;

^

note: reference must be valid for the block suffix following statement 0 at

2:16 ...

let y: & i32;

let x = 5;

y = & x;

println! ("{}", and);

}

note: ... but borrowed value is only valid for the block suffix following

statement 1 at 3:14

let x = 5;

y = & x ;

println! ("{}", and);

}

In the example above, y is declared before x , meaning that y lives longer than x , which is not allowed.

Life Times

This guide is one of three introducing the Rust membership system. This is one of the most unique and attractive features of Rust, with which Rust developers should be well acquainted. Membership is how Rust achieves its highest goal, memory management security. There are a few different concepts, each with its own chapter:

membership , the main concept

[loan] [borrowing], and its associated characteristic 'references'

times of life , the one you read now

These three chapters are related, and in order. You will need to read all three to fully understand the membership system.

Goal

Before going into detail, two important notes about the membership system.

Rust is focused on safety and speed. Rust achieves these goals through many 'zero cost abstractions', which means that at Rust, abstractions cost as little as possible to make them work. The membership system is a prime example of a zero cost abstraction. All the analysis that we will be talking about in this guide is carried out at compilation time . You do not pay any cost at runtime for any of these facilities.

However, this system has a certain cost: the learning curve. Many new Rust users experience something we call 'fighting with the borrow checker', a situation in which the Rust compiler refuses to compile a program which the author thinks is valid. This occurs frequently because the programmer's mental model of how membership works does not conform to the current rules implemented in Rust. You probably experience similar things in the beginning. However, there is good news: other experienced Rust developers report that once they work with membership system rules for a period of time, they struggle less and less with the loan checker.

With that in mind, let's learn about life times.

Life times

Lending a reference to another resource that someone else owns can be tricky. For example, imagine this set of operations:

I get a handle to some kind of resource.

I lend you a reference to the resource.

I decide that I am done with the resource, and release it, while you still have the reference to it.

You decide to use the resource.

Oh no! Your reference is pointing to an invalid resource. This is called a hanging pointer, or use after release , when the resource is memory.

To fix this, we have to make sure that step four never occurs after step three. The Rust membership system does this through a concept called lifetimes, which describe the scope in which a reference is valid.

When we have a function that takes a reference as an argument, we can be implicit or explicit about the reference's lifetime:

// implicit

fn foo (x: & i32 ) {

}

// explicit

fn bar < 'a > (x: & ' a i32 ) {

}

The 'a is read' the life time a '. Technically, every reference has a lifetime associated with it, but the compiler allows you to omit them in common cases. Before we get to that, let's take a look at the explicit piece of code:

fn bar <'a> (...)

We talked a bit about function syntax earlier , but we didn't discuss <> s after a function name. A function can have 'generic parameters' between the <> s, and lifetimes are a type of generic parameter. We will discuss other types of generics later in the book , but for now, let's focus only on the life time aspect.

We use <> to declare our life times. This says that bar has a life time, 'a . Had it had references as parameters, it would have looked like this:

fn bar <'a,' b = ""> (...)

So in our parameter list, we use the lifetimes that we have named:

... (x: & 'a i32)

If we wanted a referral & mut , we could have done the following:

... (x: & 'a mut i32)

If you compare & mut i32 with & 'to mut i32 , they are the same, it's just that time of life ' to have gotten between & and mut i32 . We read & mut i32 as 'a mutable reference to an i32 ' and & 'to mut i32 as' a mutable reference to an i32 with the lifetime 'a '.

In struct s

You will also need explicit lifetime times when working with struct s:

struct Foo < 'a > {

x: & 'to i32 ,

}

fn main () {

let y = & 5 ; // this is the same as `let _y = 5; let y = & _y; `

let f = Foo {x: y};

println! ( "{}" , fx);

}

As you can see, structs can also have lifetime. In a similar way to functions,

struct Foo < 'a > {

# x: & 'a i32 ,

#}

declares a life time, and

# struct Foo < 'a > {

x: & 'to i32 ,

#}

makes use of it. So why do we need a life time here? We need to make sure that any reference to a Foo cannot live longer than the reference to an i32 it contains.

impl blocks

Let's implement a method in Foo :

struct Foo < 'a > {

x: & 'to i32 ,

}

impl < 'a > Foo < ' a > {

fn x (& self ) -> & 'a i32 { self .x}

}

fn main () {

let y = & 5 ; // this is the same as `let _y = 5; let y = & _y; `

let f = Foo {x: y};

println! ( "x is: {}" , fx ());

}

As you can see, we need to declare a lifetime for Foo in the impl line . We repeat 'a twice, just as in functions: impl <' a> defines a lifetime 'a , and Foo <' a> makes use of it.

Multiple life time

If you have multiple references, you can use the same life time multiple times:

fn x_o_y < 'a > (x: & ' a str , y: & 'a str ) -> & ' a str {

# x

#}

The above says that both x and y live in the same scope, and that the return value is also alive for that scope. If you had wanted x and y to have different lifetimes, you could have made use of multiple lifetime parameters:

fn x_o_y < 'a , ' b > (x: & 'a str , y: & ' b str ) -> & 'a str {

# x

#}

In this example, x and y have different valid scopes, but the return value has the same life time as x .

Thinking about scopes

One way of thinking about lifetimes is to visualize the environment in which a reference is valid. For example:

fn main () {

let y = & 5 ;     // - + and enter scope

// |

// stuff // |

// |

}                   // - + and leaves scope

Adding our Foo :

struct Foo < 'a > {

x: & 'to i32 ,

}

fn main () {

let y = & 5 ;           // - + and enter scope

let f = Foo {x: y}; // - + f enters scope

// stuff // |

// |

}                         // - + fyy go out of scope

Our f lives within the scope of y , that is why everything works. What would happen otherwise? The following code would not work:

struct Foo <'a> {

x: & 'to i32,

}

fn main () {

let x; // - + x enters scope

// |

{// |

let y = & 5; // --- + and enter scope

let f = Foo {x: y}; // --- + f enters scope

x = & f.x; // | | mistake here

} // --- + fyy go out of scope

// |

println! ("{}", x); // |

} // - + x goes out of scope

Uff! As you can see here, the scopes of f and y are less than the scope of x . But when we do x = & f.x , we make x a reference to something to be out of scope.

Named lifetimes are a way to give these settings a name. Giving something a name is the first step toward being able to talk about it.

'static

The lifetime called 'static' is a special lifetime. This points out that something has the lifetime of the entire program. Most Rust developers know 'static when dealing with character strings:

let x: & 'static str = "Hello, world." ;

Character string literals have the & 'static str type since the reference is always alive: these are placed in the data segment of the final binary. Another example is the global ones:

static FOO: i32 = 5 ;

let x: & 'static i32 = & FOO;

The above adds an i32 to the data segment of the binary, and x is a reference to it.

Life times elision

Rust supports powerful type inference in function bodies, but it is prohibited in element signatures to allow reasoning based solely on the signature. However, for ergonomic reasons, a very restricted secondary inference called "life time elision" applies in function firms. The "life time elision" infers based only on the components of the signature without relying on the body of the function, it only infers life time parameters, and does this with only three easily memorizable and unambiguous rules. All of this makes life time shortcuts for writing a signature, without the need to hide the types involved since full local inference will be applied to them.

When talking about life time elision, we use the term input life time and output life time . An input lifetime is a lifetime associated with a parameter of a function, and an output lifetime is a lifetime associated with the return value of a function. For example, the following function has an input lifetime:

fn foo <'a> (bar: &' a str)

It has an output lifetime:

fn foo <'a> () -> &' a str

The following has a life time in both positions:

fn foo <'a> (bar: &' a str) -> & 'a str

Here are the three rules:

Each time of life elided in the arguments of a function becomes a different time of life parameter.

If there is exactly a single input lifetime, elided or not, that lifetime is assigned to all ellipses lifetime in that function's return values.

If there are multiple input life times, but one of them is & self or & mut self , the self life time is assigned to all elided output life times.

Otherwise, it is a mistake to avoid an output lifetime.

Examples

Here are some examples of functions with elided lifetimes. We have paired each example of an elided life time with its expanded form.

fn print (s: & str); // elided

fn print <'a> (s: &' a str); // expanded

fn debug (lvl: u32, s: & str); // elided

fn debug <'a> (lvl: u32, s: &' a str); // expanded

// In the example above, `lvl` does not need a lifetime because it is not a reference (` & `). Only things related to references (like a `struct` that contains a reference) need lifetime.

fn substr (s: & str, until: u32) -> & str; // elided

fn substr <'a> (s: &' a str, until: u32) -> & 'a str; // expanded

fn get_str () -> & str; // ILLEGAL, no inputs

fn frob (s: & str, t: & str) -> & str; // ILLEGAL, two inputs

fn frob <'a,' b = ""> (s: & 'a str, t: &' b str) -> & str; // Expanded: Output lifetime is ambiguous

fn get_mut (& mut self) -> & mut T; // elided

fn get_mut <'a> (&' a mut self) -> & 'a mut T; // expanded

fn args (& mut self, args: & [T]) -> & mut Command // elided

fn args <'a,' b, = "" T: ToCStr = ""> (& 'a mut self, args: &' b [T]) -> & 'a mut Command // expanded

fn new (buf: & mut [u8]) -> BufWriter; // elided

fn new <'a> (buf: &' a mut [u8]) -> BufWriter <'a> // expanded

Mutability

Mutability, is the ability that a thing has to be changed, works a little differently in Rust than in other languages. The first aspect of mutability is that it is not enabled by default:

let x = 5;

x = 6; // error!

We can introduce mutability with the mut keyword :

let mut x = 5 ;

x = 6 ; // no problem!

This is a link to mutable variable. When a link to variable is mutable, it means that you are allowed to change what the link points to. So, in the example above, you are not changing the value at x , instead, the link changed from one i32 to another.

If you want to change what the link points to variable, you will need a mutable reference :

let mut x = 5 ;

let y = & mut x;

y is an immutable variable link to a mutable reference, which means you can't associate y to something else ( y = & mut z ), but you can mutate whatever y is associated with ( * y = 5 ). A very subtle difference.

Of course, if you need both:

let mut x = 5 ;

let mut y = & mut x;

Now and it may be associated with another value, and the value that this referencing can be changed.

It is important to note that mut is part of a pattern , so that you can do things like:

let ( mut x, y) = ( 5 , 6 );

fn foo ( mut x: i32 ) {

#}

Internal Mutability vs. Exterior Mutability

However, when we say that something is 'immutable' in Rust, this does not mean that it cannot be changed: what we say is that something has 'external mutability'. Consider, for example, Arc <T> :

use std :: sync :: Arc;

let x = Arc :: new ( 5 );

let y = x.clone ();

When we call clone () , the Arc <T> needs to update the reference counter. Although we haven't used any mut here, x is an immutable link, we also don't take & mut 5 or any more. So what's going on?

To understand this, we must return to the core of the philosophy that guides Rust, security in memory management, and the mechanism through which Rust guarantees it, the membership system , and more specifically, the loan :

You can have one or the other of these two types of loan, but not both at the same time:

one or more references ( & T ) to a resource,

exactly a mutable reference ( & mut T ).

So that's the real definition of 'immutability': is it safe to have two pointers? In the case of Arc <T> 's, if: the mutation is completely contained within the structure itself. It is not available to the user. For this reason, clone () & T s returns . If I provided & mut T s, it would be a problem.

Other types, such as the std :: cel l module , have the opposite: internal mutability. For example:

use std :: cell :: RefCell;

let x = RefCell :: new ( 42 );

let y = x.borrow_mut ();

RefCel provides & mut references to what they contain through the borrow_mut () method . Isn't this dangerous? What if we do:

use std :: cell :: RefCell;

let x = RefCell :: new (42);

let y = x.borrow_mut ();

let z = x.borrow_mut ();

# (and Z);

This, in effect, will panic at runtime. This is what RefCell does: it enforces Rust's borrow rules at runtime, and it panics! if these rules are violated. This allows us to approach another aspect of Rust's mutability rules. Let's talk about it first.

Field level mutability

Mutability is a property of a loan ( & mut ) or a variable link ( let mut ). This translates to, for example, you can't have a struc t with some mutable and some immutable fields:

struct Point {

x: i32,

mut y: i32, // nope

}

The mutability of a struct is in its link to variable:

struct Point {

x: i32 ,

y: i32,

}

let mut a = Point {x: 5, y: 6};

ax = 10;

let b = Point {x: 5, y: 6};

bx = 10; // error: cannot assign to immutable field `bx`

However, using Cell <T > , you can emulate field level mutability:

use std :: cell :: Cell;

struct Point {

x: i32 ,

y: Cell < i32 >,

}

let dot = Dot {x: 5 , y: Cell :: new ( 6 )};

dot.y.set ( 7 );

println! ( "y: {:?}" , punto.y);

This will print y: Cell {value: 7} . We have updated and satisfactorily.

Structures

Structures ( struct s) are a way to create more complex data types. For example, if we were doing calculations involving coordinates in 2D space, we would need both an x value and a y value :

let origin_x = 0 ;

let origin_y = 0 ;

A struct allows us to combine both into a single unified data type:

struct Point {

x: i32 ,

y: i32 ,

}

fn main () {

let origin = Point {x: 0 , y: 0 }; // origin: Point

println! ( "The origin is in ({}, {})" , origin.x, origin.y);

}

There is a lot going on here, so let's break it down. We declare a structure with the keyword struct , followed by a name. By convention, structs start with a capital letter and are camel cased: DotInTheSpace , not Dot_In_The_Space .

We can instantiate our struct via let , as usual, but we use a key: value syntax to assign each field. The order does not need to be the same as in the original declaration.

Finally, because we have field names, we can access them through the dot notation: origin.x .

Values ​​in struct s are immutable by default, just like the other links to variables in Rust. Use mut to make them mutable:

struct Point {

x: i32 ,

y: i32 ,

}

fn main () {

let mut dot = Dot {x: 0 , y: 0 };

dot.x = 5 ;

println! ( "The origin is in ({}, {})" , dot.x, dot.y);

}

This will print The origin is at (5, 0) .

Rust does not support language level mutability of fields, that's why you can't write something like this:

struct Point {

mut x: i32,

y: i32,

}

Mutability is a property of the variable link, not of the structure itself. If you're used to field-level mutability, this may seem a bit strange at first, but it simplifies things significantly. It even allows you to make things mutable only for a short period of time:

struct Point {

x: i32,

y: i32,

}

fn main () {

let mut dot = Dot {x: 0, y: 0};

dot.x = 5;

let dot = dot; // now, this new link to variable cannot be changed

dot.y = 6; // this causes an error

}

Update syntax

A struct can include .. to indicate that you want to use a copy of some other struct for some of the values. For example:

struct Point3d {

x: i32 ,

y: i32 ,

z: i32 ,

}

let mut dot = Dot3d {x: 0 , y: 0 , z: 0 };

point = Point3d {y: 1 , .. point};

This assigns a new y point , but maintains the old values ​​of x and z . It does not have to be the same structure, you can make use of this syntax when you create new ones, and it will copy the values ​​you do not specify:

# struct Punto3d {

# x: i32 ,

# y: i32 ,

# z: i32 ,

#}

let origin = Point3d {x: 0 , y: 0 , z: 0 };

let dot = Dot3d {z: 1 , x: 2 , .. origin};

Tuple structures (Tuple structs)

Rust has another type of data that is like a hybrid between a tuple and a struct , called a tulla structure ( tuple struct ). Tuple structures have a name, but their fields do not:

struct Color ( i32 , i32 , i32 );

struct Point ( i32 , i32 , i32 );

The previous two will not be the same, even if they have the same values:

# struct Color ( i32 , i32 , i32 );

# struct Point ( i32 , i32 , i32 );

let black = Color ( 0 , 0 , 0 );

let origin = Point ( 0 , 0 , 0 );

It is almost always better to use a struct than a struct tuple . Instead, we could write Color and Punto like so:

struct Color {

red: i32 ,

blue: i32 ,

green: i32 ,

}

struct Point {

x: i32 ,

y: i32 ,

z: i32 ,

}

Now, we have names, instead of positions. Good names are important, and with a struct , we have names.

There is a case in which a structure tuple is very useful, and it is a structure tuple with a single element. Called pattern nuevotipo ( newtype ), since they create a new type, different from the value it contains, expressing semantics itself:

struct Inches ( i32 );

let length = Inches ( 10 );

let Inches (integer_length) = length;

println! ( "length is {} inches" , integer_length);

As you will notice, you can extract the entire content with a destruct let , just like in regular tuples. In this case, the Inches (integer_length) let assigns 10 to integer_length .

Unit-like structs

You can define a struct without any member:

struct Electron;

This struct is called type-unit ( unit-like ) because of its similarity to the empty tuple, () , sometimes called unit ( unit ). As a tuple structure, it defines a new type.

The above is rarely useful in itself (although it can sometimes serve as a marker type), but in combination with other features, it can become useful. For example, a library may require creating a structure that implements a certain trait to handle events. If you don't have any data to save in the structure, you can simply create a unit-type struct .

Enumerations

An enum ( enum ) in Rust is a type that represents data that can be one of a set of possible variants:

enum Message {

Leave,

ChangeColor ( i32 , i32 , i32 )

Move {x: i32 , y: i32 } ,

Write ( String ),

}

Each variant can optionally have associated data. The syntax for defining variants is similar to the syntax used to define structures ( struct s): you can have variants without data (such as unit-type structures), variants with named data, and variants with unnamed data (such as tuple structures). ). However, unlike structure definitions, an enum is a single type. An enum value can match any of the variants. For this reason, an enum is sometimes called a 'sum type': the set of possible values ​​of the enum is the sum of the sets of possible values ​​for each variant.

We use the :: syntax to make use of each variant: the variants are within the scope of the enum . Which makes the following valid:

# enum Message {

# Move {x: i32 , y: i32 },

#}

let x: Message = Message :: Move {x: 3 , y: 4 };

enum Turn Game Table {

Move {cells: i32 },

Pass,

}

let y: GameTurnTurn = TableTurnTurn :: Move {cells: 1 };

Both variants have the name Move , but because their scope is within the enum name, both can be used without conflict.

A value of an enum type contains information about which variant it is, in addition to any data associated with that variant. This is sometimes called 'tagged union', since the data includes a 'tag' indicating what type it is. The compiler uses this information to make sure that we are accessing the data in the enum in a secure way. For example, you can't just try to unstructure a value as if it were one of the possible variants:

fn process_color_change (msg: Message) {

let Message :: ChangeColor (r, v, a) = msg; // compile-time error

}

Not supporting this type of operations may seem a bit limiting, but it is a limitation that we can overcome. There are two ways, implementing equality on our own, or through pattern matching with matc h expressions , which you will learn in the next section. We still don't know enough about Rust to implement equality on our own, but we'll see it in the trait s section .

Constructors as functions

A constructor of an enum can also be used as a function. For example:

# enum Message {

# Write ( String ),

#}

let m = Message :: Write ( "Hello, world" .to_string ());

Is the same as

# enum Message {

# Write ( String ),

#}

fn foo (x: String ) -> Message {

Message :: Write (x)

}

let x = foo ( "Hello, world" .to_string ());

This is not immediately useful to us, but when we get to closure s , we will talk about passing functions as arguments to other functions. For example, with iterators , we can convert a vector from String s to a vector from Message :: Write s:

# enum Message {

# Write ( String ),

#}

let v = vec! [ "Hello" .to_string (), "World" .to_string ()];

let v1: Vec <Message> = v.into_iter (). map (Message :: Write) .collect ();

Match

Often a simple i f / else is not enough, because you have more than two possible options. Also, the conditions can be complex. Rust has a reserved word, match , which allows you to replace complicated if / else constructions with something more powerful. Check out:

let x = 5 ;

match x {

1 => println! ( "one" ),

2 => println! ( "two" ),

3 => println! ( "trees" ),

4 => println! ( "four" ),

5 => println! ( "five" ),

_ => println! ( "something else" ),

}

match takes an expression and then forks based on its value. Each branch arm has the form value => expression . When the value matches, the expression of the arm is evaluated. It is called match by the term 'pattern matching', of which match is an implementation. There is an entire section about patterns that covers all possible patterns.

So what is the big advantage? Well, there are a few. First of all, match imposes check exhaustion ( 'exhaustiveness checking'). Do you see the last arm, the one with the underscore ( _ )? If we remove that arm, Rust will give us an error:

error: non-exhaustive patterns: `_` not covered

In other words, Rust is trying to tell us that we forgot a value. Because x is an integer, Rust knows that x can have a number of different values ​​- for example, 6 . Without the _ , however, there is no matching arm, and consequently Rust refuses to compile the code. _ acts as an 'arm that catches everything'. If none of the other arms match, the arm with the _ will, and since we have said 'catch everything arm', we now have an arm for every possible value of x , and as a result, our program will compile successfully.

match is also an expression, which means that we can use it on the right side of a let or directly where an expression is used:

let x = 5 ;

let number = match x {

1 => "one" ,

2 => "two" ,

3 => "three" ,

4 => "four" ,

5 => "five" ,

_ => "something else" ,

};

Sometimes it is a good way to convert something from one type to another.

Making match in enums

Another important use of the match keyword is to process possible variants of an enum:

enum Message {

Leave,

ChangeColor ( i32 , i32 , i32 )

Move {x: i32 , y: i32 },

Write ( String ),

}

fn exit () { / * ... * / }

fn change_color (r: i32 , g: i32 , b: i32 ) { / * ... * / }

fn move_cursor (x: i32 , y: i32 ) { / * ... * / }

fn process_message (msg: Message) {

match msg {

Message :: Exit => exit (),

Message :: ChangeColor (r, g, b) => change_color (r, g, b),

Message :: Move {x: x, y: y} => move_cursor (x, y),

Message :: Write (s) => println! ( "{}" , s),

};

}

Again, the Rust compiler checks exhaustion, demanding that you have an arm for each variant of the enumeration. If you leave one out, Rust will generate a compile-time error, unless you use _ .

Unlike previous uses of match , you can't use the if statement to do this. You can make use of an if let statemen t that can be seen as a short form of match .

Vector

A 'vector' is a dynamic array, implemented as the type of the standard Vec <T > library . The T means that we can have vectors of any type (take a look at the [generics] [generics] chapter for more information). Vectors always house their data on the mound. You can create vectors with the vec macro ! :

let v = vec! [ 1 , 2 , 3 , 4 , 5 ]; // v: Vec <i32>

(Note that unlike the println! Macro we have used in the past, we use square brackets [] with the vec! Macro . Rust allows you to use any in any situation, this time it is purely by convention)

There is an alternative form of vec! to repeat an initial value:

let v = vec! [ 0 ; 10 ]; // ten zeros

Accessing elements

To get the value at a particular index of the vector, we use [] s:

let v = vec! [ 1 , 2 , 3 , 4 , 5 ];

println! ( "The third element of v is {}" , v [ 2 ]);

Indices start from 0 , so the third element is v [2] .

Iterating

Once you have a vector, you can iterate through its elements with for . There are three versions:

let mut v = vec! [ 1 , 2 , 3 , 4 , 5 ];

for i in & v {

println! ( "A reference to {}" , i);

}

for i in & mut v {

println! ( "A mutable reference to {}" , i);

}

for i in v {

println! ( "Taking membership of the vector and its element {}" , i);

}

Character Strings

Character strings are an important concept to master for any programmer. The character string handling system in Rust is slightly different from that of other languages, due to its focus on system programming. As long as you have a variable-size data structure, things can get a little difficult, and character strings are a data structure that can vary in size. That said, Rust's character strings also work differently than in some other system programming languages, such as C.

Let's go into the details. A 'character string' ('string') is a sequence of Unicode scalar values ​​encoded as a stream of UTF-8 bytes. All character strings are guaranteed to be a valid encoding of UTF-8 sequences. Additionally, and unlike other system languages, character strings are not null-terminated and can contain null bytes.

Rust has two main types of character strings: & str and String . Let's talk about & str first . These are called 'string slices'. String literals are of type & 'static str`:

let greeting = "Hello." ; // greeting: & 'static str

This character string is statically assigned, meaning that it is stored within our compiled program, and exists for the full duration of its execution. The greeting link is a reference to an aesthetically assigned string. Pieces of character strings are of a fixed size, and cannot be mutated.

A String , on the other hand, is a character string assigned from the mound. This chain can grow, and it is also guaranteed to be UTF-8. The String are commonly created through conversion of a piece of character string using the method to_string .

let mut s = "Hello" .to_string (); // mut s: String

println! ( "{}" , s);

s.push_str ( ", world." );

println! ( "{}" , s);

The String s haran coercion to a & str with & :

fn get_scrap (bit: & str ) {

println! ( "I received: {}" , piece);

}

fn main () {

let s = "Hello" .to_string ();

receive_piece (& s);

}

This coercion does not occur for functions that accept one of the traits & str ’s instead of & str . For example, TcpStream :: connec t has a parameter of type ToSocketAddrs . A & str is fine but a String must be explicitly converted using & * .

use std :: net :: TcpStream;

TcpStream :: connect ("192.168.0.1:3000"); // parameter & str

let address_string = "192.168.0.1:3000" .to_string ();

TcpStream :: connect (& * address_string); // converting address_string to & str

Viewing a String as a & str is cheap, but converting the & str to a String involves memory allocation. There is no reason to do that unless necessary!

Indexed

Because the character strings are valid UTF-8, they do not support indexing:

let s = "hello";

println! ("The first letter of s is {}", s [0]); // ERROR!!!

Access to a vector with [] is usually very fast. But, since each character encoded in a UTF-8 string can have multiple bytes, you must traverse the entire string to find the nᵗʰ letter of a string. This is a significantly more expensive operation, and we do not want to create confusion. Even 'letter' is not exactly something defined in Unicode. We can choose to see a character string as individual bytes, or as codepoints:

let hachiko = " 忠　犬　ハ　チ　公" ;

for b in hachiko.as_bytes () {

print! ( "{}," , b);

}

println! ( "" );

for c in hachiko.chars () {

print! ( "{}," , c);

}

println! ( "" );

The above prints:

229, 191, 160, 231, 138, 172, 227, 131, 143, 227, 131, 129, 229, 133, 172,

忠, 犬, ハ, チ, 公,

As you can see, there are more bytes than characters ( char s).

You can get something similar to an index like this:

# let hachiko = " 忠　犬　ハ　チ　公" ;

let dog = hachiko.chars (). nth ( 1 ); // something like hachiko [1]

This emphasizes that we have to walk from the beginning of the chars list .

Slicing

You can get a piece of a character string with the cut syntax:

let dog = "hachiko" ;

let hachi = & dog [ 0 .. 5 ];

But note that these are byte offsets, not character offsets . So the following will fail at runtime:

let dog = " 忠　犬　ハ　チ　公";

let hachi = & dog [0..2];

with this error:

thread '' panicked at 'index 0 and / or 2 in ` 忠　犬　ハ　チ　公 ` do not lie on

character boundary '

Concatenation

If you have a String , you can concatenate a & str at the end:

let hello = "Hello" .to_string ();

let mundo = "world!" ;

let hello_world = hello + world;

But if you have two String s, you need a & :

let hello = "Hello" .to_string ();

let mundo = "world!" .to_string ();

let hello_world = hello + & world;

This is because & String can automatically coerce a & str . This feature is called ' Deref coercions '.

Generic

Sometimes when writing a function or data structure, we might wish that it could work with multiple types of arguments. At Rust we can achieve this through generics. Generics are called 'parametric polymorphism' in type theory, which means that they are types or functions that have multiple shapes ('poly' for multiple, 'morph' for shape) on a certain parameter ('parametric').

Either way, enough about type theory, let's look at some generic code. The standard Rust library provides a type, Option <T> , which is generic:

enum Option <T> {

Some (T),

None ,

}

The <T> part , which you have seen a few times before, indicates that this is a generic data type. Within the declaration of our enum, wherever we see a T we substitute that type for the same type used in the generic one. Here's an example of using Option <T> , with some extra annotations:

let x: Option < i32 > = Some ( 5 );

In the type declaration, we say Option <i32> . Note how similar this looks to Option <T> . So in this Option , T has the value of i32 . On the right side of the binding, we make a Some (T) , where T is 5 . Because 5 is an i32 , both sides match, and Rust is happy. If they did not match, we would have got an error:

let x: Option = Some (5);

// error: mismatched types: expected `core :: option :: Option`,

// found `core :: option :: Option <_>` (expected f64 but found integral variable)

That doesn't mean we can't create Option <T> s that contain an f64 . They simply must match:

let x: Option < i32 > = Some ( 5 );

let y: Option < f64 > = Some ( 5.0f64 );

Very good. One definition, multiple uses.

Generics do not necessarily have to be generic on a single type. Consider another similar type in Rust's standard library, Result <T, E> :

enum Result <T, E> {

Ok (T),

Err (E),

}

This type is generic on two types: T and E . By the way, capital letters can be any. We could have defined Result <T, E> as:

enum Result <A, Z> {

Ok (A),

Err (Z),

}

to have wanted. The convention says that the first generic parameter must be T , of 'type', and that we use E for 'error'. Rust, however, does not care.

The Result <T, E> type is used to return the result of a computation, with the possibility of returning an error in the event that such computation has not been successful.

Generic functions

We can write functions that take generic types with a similar syntax:

fn receives_any_thing <T> (x: T) {

// do something with x

}

The syntax has two parts: the <T> says "this function is generic on a type, T ", and the x: T part says "x has the type T. "

Multiple arguments can have the same type:

fn receives_two_things_from_the same_type <T> (x: T, y: T) {

// ...

}

We could have written a version that receives multiple types:

fn receives_two_things_from_different_types <T, U> (x: T, y: U) {

// ...

}

Generic structures

You can also store a generic type in a structure:

struct Point <T> {

x: T,

y: T,

}

let integer_source = Point {x: 0 , y: 0 };

let float_origin = Point {x: 0.0 , y: 0.0 };

Similarly to functions, the <T> section is where we declare generic parameters, after that we make use of x: T in the type declaration, too.

When we want to add an implementation for the generic structure, you simply declare the type parameter after impl :

# struct Point <T> {

# x: T,

# y: T,

#}

#

impl <T> Point <T> {

fn exchange (& mut self ) {

std :: mem :: swap (& mut self .x, & mut self .y);

}

}

So far you have only seen generics that accept absolutely any type. These are useful in many cases, you have already seen Option <T> , and later you will meet universal containers like Vec <T > . On the other hand, sometimes you will want to exchange that flexibility for greater expressive power. Read about trait limits to see why and how.

Traits

A trait is a language facility that tells the Rust compiler about the functionality that a type must provide.

Remember the impl ? Keyword , used to call a function with the method syntax ?

struct Circle {

x: f64 ,

y: f64 ,

radius: f64 ,

}

impl Circle {

fn area (& self ) -> f64 {

std :: f64 :: consts :: PI * ( self .radio * self .radio)

}

}

The traits are similar, except that we define a trait with only the method signature and then implement the trait for the structure. So:

struct Circle {

x: f64 ,

y: f64 ,

radius: f64 ,

}

trait HasArea {

fn area (& self ) -> f64 ;

}

impl HasArea for Circle {

fn area (& self ) -> f64 {

std :: f64 :: consts :: PI * ( self .radio * self .radio)

}

}

As you can see, the trait block looks very similar to the impl block , but we don't define a block, just the type signature. When we implement a trait, we use Impl Trait for Item , instead of just Impl Item .

Trait limits for generic functions

Traits are useful because they allow a guy to make certain promises about his behavior. Generic functions can exploit this to restrict the types they accept. Consider this function, which doesn't compile:

fn imrimir_area (figure: T) {

println! ("This figure has an area of ​​{}", figura.area ());

}

Rust complains:

error: no method named `area` found for type` T` in the current scope

Because T can be of any type, we cannot be sure that it implements the area method . But we can add a 'trait constraint' to our generic T , making sure it implements it:

# trait HasArea {

#     fn area (& self ) -> f64 ;

#}

fn print_area <T: HasArea> (shape: T) {

println! ( "This figure has an area of ​​{}" , figura.area ());

}

The <T: hasArea> syntax translates to "any type that implements the hasArea trait .". As traits define function type signatures, we can be sure that any type that implements hasArea will have a .area () method .

Here's an extended example of how this works:

trait HasArea {

fn area (& self ) -> f64 ;

}

struct Circle {

x: f64 ,

y: f64 ,

radius: f64 ,

}

impl HasArea for Circle {

fn area (& self ) -> f64 {

std :: f64 :: consts :: PI * ( self .radio * self .radio)

}

}

Square struct {

x: f64 ,

y: f64 ,

side: f64 ,

}

Impl HasArea for Square {

fn area (& self ) -> f64 {

self. side * self. side

}

}

fn imrimir_area <T: tieneArea> (figure: T) {

println! ( "This figure has an area of ​​{}" , figura.area ());

}

fn main () {

let c = Circle {

x: 0.0f64 ,

y: 0.0f64 ,

radius: 1.0f64 ,

} ;

let s = Square {

x: 0.0f64 ,

y: 0.0f64 ,

side: 1.0f64 ,

};

print_area (c);

print_area (s);

}

This program produces the output:

This figure has an area of ​​3.141593

This figure has an area of ​​1

As you can see, print_area is now generic, but it also ensures that we have provided the correct types. If we pass an incorrect type:

print_area (5);

We get a compile-time error:

error: the trait `HasArea` is not implemented for the type` _` [E0277]

Trait limits for generic structures

Your generic structures can also benefit from trait restrictions. All you need to do is add the constraint when you declare the type parameters. Here is a new Rectangle <T> type and its operation is_square :

struct Rectangle <T> {

x: T,

y: T,

width: T,

height: T,

}

impl <T: PartialEq > Rectangle <T> {

fn is_square (& self ) -> bool {

self. width == self. height

}

}

fn main () {

let mut r = Rectangle {

x: 0 ,

y: 0 ,

width: 47 ,

height: 47 ,

};

assert! (r.es_cuadrado ());

r.height = 42 ;

assert! (! r.es_cuadrado ());

}

is_square () needs to check that the sides are equal, and for this the types must be of a type that implements the trait core :: cmp :: PartialE q :

Impl Rectangle {...}

Now, a rectangle can be defined based on any type that can be compared by equality.

We have defined a new Rectangle structure that accepts numbers of any precision, objects of any type as long as they can be compared by equality. Could we do the same for our HasArea , Square and Circle structures ? Yes, but they need multiplication, and to work with that we need to know more about the operator traits .

Rules for the implementation of traits

So far, we have only added trait implementations to structures, but you can implement any trait for any type. Technically, we could implement TieneArea for i32 :

trait HasArea {

fn area (& self ) -> f64 ;

}

impl TieneArea for i32 {

fn area (& self ) -> f64 {

println! ( "this is silly" );

* self as f64

}

}

5 .area ();

Implementing methods on those primitive types is considered poor style, even when possible.

This may look like the old west, but there are two restrictions on implementing traits that prevent things from spiraling out of control. The first is that if the trait is not defined in your scope, it does not apply. Here's an example: the standard library provides a Writ e trait that adds extra functionality to File s, making file I / O possible. By default, a File would not have its methods:

let mut f = std :: fs :: File :: open ("foo.txt"). ok (). expect ("Failed to open foo.txt");

let buf = b "anything"; // byte string literal. buf: & [u8; 8]

let result = f.write (buf);

# result.unwrap (); // ignore the error

Here is the error:

error: type `std :: fs :: File` does not implement any method in scope named` write`

let result = f.write (buf);

^ ~~~~~~~~~

We need to first use the Write trait :

use std :: io :: Write;

let mut f = std :: fs :: File :: open ("foo.txt"). ok (). expect ("Failed to open foo.txt");

let buf = b "anything";

let result = f.write (buf);

# result.unwrap (); // ignore the error

The above will compile without errors.

This means that even if someone does something wrong like adding methods to i32 , it won't affect you, unless you use that trait.

There is one more restriction on the implementation of traits: one of the two, either the trait or the type for which you are writing the impl , must be defined by you. So, we could implement the hasArea trait for type i32 , since HasArea is in our code. But if we tried to implement ToString , a trait provided by Rust for i32 , we couldn't, because neither the trait nor the type is in our code.

One last thing about traits: generic functions with a trait limit use 'monomorphization' ('monomorphization') (mono: one, morphs: shape), and are therefore statically dispatched. What does this mean? Take a look at the chapter on trait objects for more details.

Multiple trait limits

You have seen that you can limit a generic type parameter with a trait:

fn foo <T: Clone > (x: T) {

x.clone ();

}

If you need more than one limit, you can use + :

use std :: fmt :: Debug;

fn foo <T: Clone + Debug> (x: T) {

x.clone ();

println! ( "{:?}" , x);

}

T now needs to be both Clone and Debug .

The where clause

Writing functions with just a few generic types and a small number of trait limits is not that ugly, but as the number increases, the syntax gets a little weird:

use std :: fmt :: Debug;

fn foo <T: Clone , K: Clone + Debug> (x: T, y: K) {

x.clone ();

y.clone ();

println! ( "{:?}" , and);

}

The function name is far to the left, and the parameter list is far to the right. Trait limits get in the way.

Rust has a solution, and it's called ' where clause ':

use std :: fmt :: Debug;

fn foo <T: Clone , K: Clone + Debug> (x: T, y: K) {

x.clone ();

y.clone ();

println! ( "{:?}" , and);

}

fn bar <T, K> (x: T, y: K) where T: Clone , K: Clone + Debug {

x.clone ();

y.clone ();

println! ( "{:?}" , and);

}

fn main () {

foo ( "Hello" , "world" );

bar ( "Hello" , "world" );

}

foo () uses the syntax previously demonstrated, and bar () uses a where clause . All you need to do is leave the limits out when you define your type parameters and then add a where after the parameter list. For longer lists, blank spaces can be added:

use std :: fmt :: Debug;

fn bar <T, K> (x: T, y: K)

where T: Clone ,

K: Clone + Debug {

x.clone ();

y.clone ();

println! ( "{:?}" , and);

}

Such flexibility can add clarity in complex situations.

The where clause is also more powerful than the simplest syntax. For example:

Trait become <Output> {

fn convert (& self ) -> Output;

}

impl become < i64 > for i32 {

fn convert (& self ) -> i64 {* self as i64 }

}

// can be called with T == i32

fn normal <T: ConvertA < i64 >> (x: & T) -> i64 {

x.convert ()

}

// can be called with T == i64

inverse fn <T> () -> T

// pesto is user ConvertA as if it were "ConvertA <i64>"

where i32 : ConvertA <T> {

42 .convert ()

}

The above demonstrates an additional feature of where : it allows limits where the left side is an arbitrary type ( i32 in this case), not just a simple type parameter (like T ).

Default methods

If you already know how a typical implementer will define a method, you can allow your trait to provide a default method:

trait Foo {

fn is_valid (& self ) -> bool ;

fn is_invalid (& self ) -> bool {! self .es_valido ()}

}

Foo trait implementers need to implement es_valido () , but they do not need to implement es_valido () . They will get it by default. They can also override the default implementation if they want:

# trait Foo {

#     fn is_valid (& self ) -> bool ;

#

#     fn is_invalid (& self ) -> bool {! self .es_valido ()}

#}

struct UsaDefault;

impl Foo for UsaDefault {

fn is_valid (& self ) -> bool {

println! ( "UsaDefault.es_valid called." );

true

}

}

struct OverwriteDefault;

impl Foo for OverwriteDefault {

fn is_valid (& self ) -> bool {

println! ( "Override default._valid call." );

true

}

fn is_invalid (& self ) -> bool {

println! ( "OverwriteDefault.es_valid call!" );

true // this implementation is a self-contradiction !

}

}

let default = UsaDefault;

assert! (! default.es_valido ()); // print "UsaDefault.es_valid call."

let on = OverwriteDefault;

assert! (sobre.is_invalid ()); // prints "OverwriteDefault.com_invalid call!"

Heritage

Sometimes implementing one trait requires implementing another:

trait Foo {

fn foo (& self );

}

trait FooBar : Foo {

fn foobar (& self );

}

FooBar implementers must also implement Foo , like this:

# trait Foo {

#     fn foo (& self );

#}

# trait FooBar : Foo {

#     fn foobar (& self );

#}

struct Baz;

impl Foo for Baz {

fn foo (& self ) { println! ( "foo" ); }

}

impl FooBar for Baz {

fn foobar (& self ) { println! ( "foobar" ); }

}

If we forget to implement Foo , Rust will tell us:

error: the trait `main :: Foo` is not implemented for the type` main :: Baz` [E0277]

Drop

Now that we've discussed traits, let's talk about a particular trait provided by the standard Rust library, Drop . The Drop trait provides a way to execute code when a value goes out of scope. For example:

struct HasDrop;

impl Drop for HasDrop {

fn drop (& mut self ) {

println! ( "Dropping!" );

}

}

fn main () {

let x = HasDrop;

// let's do something

} // x leaves scope here

When x goes out of scope at the end of main () , the Drop code is executed. Drop has a method, also called drop () . This method takes a mutable reference to self .

That's it! Drop's mechanics are very simple, however there are a few details. For example, values ​​are dropped in the opposite order of how they were declared. Here is another example:

Explosive struct {

power: i32 ,

}

impl Drop for Explosive {

fn drop (& mut self ) {

println! ( "BOOM multiplied by {} !!!" , self .power);

}

}

fn main () {

let firecracker = Explosive {power: 1 };

let tnt = Explosive {power: 100 };

}

The above will print:

BOOM multiplied by 100 !!!

BOOM multiplied by 1 !!!

The TNT goes first than the firecracker, because it was created later. Last to enter, first to exit.

So what is Drop good for ? Generally, it is used to clean up any resource associated with a struct . For example, the type Arc <T > is a guy with reference counting. When Drop is called, it will decrement the reference count, and if the total number of references is zero, it will clear the underlying value.

if let

if let allows you to combine if and let to reduce the cost of some types of pattern matching.

For example, let's say we have some kind of Option <T> . We want to call a function on it if it is a Some <T> , but do nothing if it is None . It would be something like this:

# let option = Some ( 5 );

# fn foo (x: i32 ) {}

match option {

Some (x) => {foo (x)},

None => {},

}

We don't have to use match here, for example we could use if :

# let option = Some ( 5 );

# fn foo (x: i32 ) {}

if option.is_some () {

let x = option.unwrap ();

foo (x);

}

Neither of these two options is particularly attractive. We can use if let to do the same, but in a better way:

# let option = Some ( 5 );

# fn foo (x: i32 ) {}

if let Some (x) = option {

foo (x);

}

If a pattern matches well, it associates any appropriate part of the value with the identifiers in the pattern, and then evaluates the expression. If the pattern does not match, nothing happens.

If you want to do something in case the pattern doesn't match, you can use else :

# let option = Some ( 5 );

# fn foo (x: i32 ) {}

# fn bar () {}

if let Some (x) = option {

foo (x);

} else {

Pub();

}

while let

Similarly, while let can be used when you want to conditionally iterate as long as the value matches a certain pattern. Convert code like this:

# let option: Option < i32 > = None ;

loop {

match option {

Some (x) => println! ( "{}" , x),

_ => break ,

}

}

In code like this:

# let option: Option < i32 > = None ;

while let Some (x) = option {

println! ( "{}" , x);

}

Trait Objects

When the code involves polymorphism, a mechanism is needed to determine which specific version should be run. This mechanism is called an office . There are two major forms of dispatch: static dispatch and dynamic dispatch. While it is true that Rust prefers static dispatching, it also supports dynamic dispatching through a mechanism called 'trait objects'.

Bases

For the rest of this chapter, we will need a trait and some implementations. Let's create a simple one, Foo . Foo has a single method that returns a String .

trait Foo {

fn method (& self ) -> String ;

}

We will also implement this trait for u8 and String :

# trait Foo { fn method (& self ) -> String ; }

impl Foo for u8 {

fn method (& self ) -> String { format! ( "u8: {}" , * self )}

}

impl Foo for String {

fn method (& self ) -> String { format! ( "string: {}" , * self )}

}

Static dispatch

We can use trait to perform static dispatch by using trait limits:

# trait Foo { fn method (& self ) -> String ; }

# impl Foo for u8 { fn method (& self ) -> String { format! ( "u8: {}" , * self )}}

# impl Foo for String { fn method (& self ) -> String { format! ( "string: {}" , * self )}}

fn do_something <T: Foo> (x: T) {

x.method ();

}

fn main () {

let x = 5u8 ;

let y = "Hello" .to_string ();

do_something (x);

do_something (y);

}

Rust uses 'monomorphization' for static dispatch in this code. Which means you will create a special version of do_something () for both u8 and String , then replacing the call places with calls to these specialized functions. In other words, Rust generates something like this:

# trait Foo { fn method (& self ) -> String ; }

# impl Foo for u8 { fn method (& self ) -> String { format! ( "u8: {}" , * self )}}

# impl Foo for String { fn method (& self ) -> String { format! ( "string: {}" , * self )}}

fn do_something_u8 (x: u8 ) {

x.method ();

}

fn do_something_string (x: String ) {

x.method ();

}

fn main () {

let x = 5u8 ;

let y = "Hello" .to_string ();

do_something_u8 (x);

do_something_string (y);

}

The above has a great advantage: static dispatch allows function calls to be inserted online because the receiver is known at compile time, and online insertion is key to good optimization. Static dispatch is fast, but it comes with a downside: 'code bloat', as a result of repeated copies of the same function being inserted into the binary, one for each type.

Also, compilers are not perfect and can "optimize" code by slowing it down. For example, functions inserted online in an anxious way inflate the instruction cache (and the cache governs everything around us). This is why # [inline] and # [inline (always)] should be used with caution, and one reason why using dynamic dispatch is sometimes more efficient.

However, the common case is that static dispatch is more efficient. One can have a thin wrapper function dispatched statically by performing dynamic dispatch, but not vice versa, ie; static calls are more flexible. This is why the standard library tries to be dynamically dispatched whenever possible.

Dynamic dispatch

Rust provides dynamic dispatch through a facility called 'trait objects'. Trait objects, like & Foo or Box <Foo> , are normal values ​​that store a value of any type that implements the given trait, where the precise type can only be determined at runtime.

A trait object can be obtained from a pointer to a specific type that implements the trait by converting it (eg & x as & Foo ) or applying coercion (eg using & x as an argument to a function that receives & Foo ).

Those coercions and conversions also work for pointers like & mut T a & mut Foo and Box <T> a Box <Foo> , but that's it so far. Coercions and conversions are identical.

This operation can be seen as the 'deletion' of the compiler's knowledge about the specific type of the pointer, and that is why traits objects are sometimes referred to as type deletion .

Going back to the previous example, we can use the same trait to perform dynamic dispatch with conversion of trait objects:

# trait Foo { fn method (& self ) -> String ; }

# impl Foo for u8 { fn method (& self ) -> String { format! ( "u8: {}" , * self )}}

# impl Foo for String { fn method (& self ) -> String { format! ( "string: {}" , * self )}}

fn do_something (x: & Foo) {

x.method ();

}

fn main () {

let x = 5u8 ;

do_something (& x as & Foo);

}

or through coercion:

# trait Foo { fn method (& self ) -> String ; }

# impl Foo for u8 { fn method (& self ) -> String { format! ( "u8: {}" , * self )}}

# impl Foo for String { fn method (& self ) -> String { format! ( "string: {}" , * self )}}

fn do_something (x: & Foo) {

x.method ();

}

fn main () {

let x = "Hello" .to_string ();

do_something (& x);

}

A function that receives a trait object is not specialized for each of the types that Foo implements : only one copy is generated, sometimes (but not always) resulting in less code inflation. However, dynamic dispatch comes at the cost of requiring the slowest calls to virtual functions, effectively inhibiting any possibility of online insertion and related optimizations.

Why pointers?

Rust, unlike many managed languages, doesn't put things behind default pointers, which results in types having different sizes. Knowing the size of a value at compile time is important for things like passing it as an argument to a function, moving it on the stack, and allocating (and de-allocating) space for it in the mound for storage.

For Foo , we would need to have a value that could be smaller than a String (24 bytes) or a u8 (1 byte), as well as any other type that Foo can implement in dependent crates (any number of bytes). There is no way to guarantee that the latter case can work if the values ​​are not stored in a pointer, since those other types can be of arbitrary size.

Placing the value behind a pointer means that the size of the value is not relevant when we are throwing a trait object around, just the size of the pointer itself.

Representation

Trait methods can be called on a trait object through a function pointer register traditionally called 'vtable' (created and managed by the compiler).

Trait objects are simple and complex at the same time: their representation and distribution is quite straightforward, but there are some rather rare error messages and some surprising behaviors to discover.

Let's start with the simplest, the representation of a trait object at runtime. The std :: raw module contains structs with distributions that are just as complicated as those of built-in types, including trait objects :

# mod foo {

pub struct TraitObject {

pub data: * mut (),

pub vtable: * mut (),

}

#}

That is, a trait object like & Foo consists of a 'data' pointer and a 'vtable' pointer.

The data pointer points to the data (of an unknown type T ) stored by the trait object, and the vtable pointer points to the vtable (table of virtual methods') ('virtual method table') corresponding to the implementation of Foo for T .

A vtable is essentially a struct of function pointers, pointing to the particular segment of machine code for each method implementation. A call to method like object_trait.method () will return the correct pointer from the vtable and then make a dynamic call of it. For example:

struct FooVtable {

destroyer: fn (* mut ()),

size: usize,

alignment: usize,

method: fn (* const ()) -> String,

}

// u8:

fn call_method_in_u8 (x: * const ()) -> String {

// the compiler guarantees that this function is only called

// with `x` pointing to a u8

let byte: & u8 = unsafe {& * (x as * const u8)};

byte.metodo ()

}

static Foo_vtable_para_u8: FooVtable = FooVtable {

destroyer: / * compiler magic * /,

size: 1,

alignment: 1,

// conversion to a function pointer

method: call_method_in_u8 as fn (* const ()) -> String,

};

// String:

fn call_method_in_String (x: * const ()) -> String {

// the compiler guarantees that this function is only called

// with `x` pointing to a String

let string: & String = unsafe {& * (x as * const String)};

string.metodo ()

}

static Foo_vtable_para_String: FooVtable = FooVtable {

destroyer: / * compiler magic * /,

// values ​​for a 64-bit computer, divide them in half for a 32-bit

size: 24,

alignment: 8,

method: call_method_in_String as fn (* const ()) -> String,

};

The destructor field in each vtable points to a function that will clear all resources of the vtable type: for u8 it is trivial, but for String it will free up memory. This is necessary to take over trait objects like Box <Foo> , which need to clear both the Box mapping as well as the inner type when they get out of scope. The size and alignment fields store the size of the deleted type, and its alignment requirements; these are essentially unused at the moment since the information is embedded in the shredder, but will be used in the future, as the trait objects are progressively made more flexible.

Suppose we have some values ​​that implement Foo . The explicit way of constructing and using trait Foo objects may look a bit like (ignoring inconsistencies between types: they are pointers anyway):

let a: String = "foo" .to_string ();

let x: u8 = 1;

// let b: & Foo = & a;

let b = TraitObject {

// store the data

data: & a,

// store the methods

vtable: & Foo_vtable_para_String

};

// let y: & Foo = x;

let y = TraitObject {

// store the data

data: & x,

// store the methods

vtable: & Foo_vtable_para_u8

};

// b.metodo ();

(b.vtable.metodo) (b.data);

// and.method ();

(y.vtable.metodo) (y.data);

Object Security

Not every trait can be used to create a trait object. For example, vectors implement Clone , but if we try to create a trait object:

let v = vec! [1, 2, 3];

let o = & v as & Clone;

We get an error:

error: cannot convert to a trait object because trait `core :: clone :: Clone` is not object-safe [E0038]

let o = & v as & Clone;

^ ~

note: the trait cannot require that `Self: Sized`

let o = & v as & Clone;

^ ~

The error says that Clone is not 'object-safe'. Only traits that are safe for objects can be used in creating trait objects. A trait is safe for objects if both conditions are true:

the trait does not require Self: Sized

all its methods are safe for objects

So what makes a method safe for objects? Each method should require that Self: Sized or all of the following:

must not have any type parameters

you shouldn't use Self

Uff! As we can see, almost all of these rules talk about Self . A good intuition would be "except for special circumstances, if your trait method uses Self , it is not safe for objects."

Closures

Sometimes it is useful to wrap a function and its free variables for better clarity and reusability. The free variables that can be used come from the outside scope and are 'closed over' when used in the function. Hence the name 'closure'. Rust provides a very good implementation , as we will see below.

Syntax

The closures look like this:

let sum_one = | x: i32 | x + 1 ;

assert_eq! ( 2 , sum_one ( 1 ));

We create a link to variable, sum_one and assign it to a closure. The arguments of the closure go between pipes ( | ), and the body is an expression, in this case x + 1 . Remember that {} is an expression, so we can also have multi-line closures:

let sum_two = | x | {

let mut result: i32 = x;

result + = 1 ;

result + = 1 ;

Outcome

};

assert_eq! ( 4 , sum_two ( 2 ));

You'll notice a couple of things about closures that are slightly different from the regular functions defined with fn . The first thing is that we don't need to write down the types of the arguments and the return values. We can:

let sum_one = | x: i32 | -> i32 {x + 1 };

assert_eq! ( 2 , sum_one ( 1 ));

But we don't need to. Why? Basically, it was implemented that way for ergonomic reasons. While specifying the full type for named functions is useful for things like documentation and type inference, the full signature in closures is rarely documented since they are almost always anonymous, and do not cause error-a type problems. -distance that inference in named functions can cause.

The second syntax is similar, but somewhat different. I've added spaces here for easy comparison:

fn sum_one_v1 (x: i32 ) -> i32 {x + 1 }    

let sum_one_v2 = | x: i32 | -> i32 {x + 1 };

let sum_one_v3 = | x: i32 | x + 1   ;

Small differences, but they are similar.

Closures and its surroundings

The environment for a closure can include links to the scope variable that wraps them in addition to the local variables and parameters. In this way:

let num = 5 ;

let sum_num = | x: i32 | x + num;

assert_eq! ( 10 , sum_num ( 5 ));

The closure suma_num refers to the let link in its scope: num . More specifically, borrow the link. If we do something that conflicts with that link, we would get an error like this:

let mut num = 5;

let sum_num = | x: i32 | x + num;

let y = & mut num;

Which fails with:

error: cannot borrow `num` as mutable because it is also borrowed as immutable

let y = & mut num ;

^ ~~

note: previous borrow of `num` occurs here due to use in closure; the immutable

borrow prevents subsequent moves or mutable borrows of `num` until the borrow

ends

let sum_num = | x | x + num;

^ ~~~~~~~~~~

note: previous borrow ends here

fn main () {

let mut num = 5;

let sum_num = | x | x + num;

let y = & mut num;

}

^

A somewhat verbose error but just as useful! As it says, we cannot borrow a mutable loan in num because the closure is already borrowing it. If we leave the closure out of scope, then it is possible:

let mut num = 5 ;

{

let sum_num = | x: i32 | x + num;

} // sum_num goes out of scope, the loan ends here

let y = & mut num;

However, if your closure requires it, Rust will take belong and move the environment. The following does not work:

let nums = vec! [1, 2, 3];

let toma_nums = || nums;

println! ("{:?}", nums);

We get this error:

note: `nums` moved into closure environment here because it has type

`[closure (()) -> collections :: vec :: Vec]`, which is non-copyable

let toma_nums = || nums;

^ ~~~~~~

Vec <T> has membership of its content, and that is why, when referring to it within our closure, we have to take membership of nums . It is the same as if we had provided nums as an argument to a function that would take membership on it.

Closures move

We can force our closure to take belong to its environment with the move keyword :

let num = 5 ;

let take_name_num = move | x: i32 | x + num;

Now, even when the move keyword is present, variables follow normal semantics. In this case, 5 implements Copy , and consequently num_take takes membership of a copy of num . So what is the difference?

let mut num = 5 ;

{

let mut sum_num = | x: i32 | num + = x;

sum_num ( 5 );

}

assert_eq! ( 10 , num);

In this case, our closure took a mutable reference to num , and when we call sum_num , it mutes the underlying value, just as we expected. We also need to declare sum_num as mut , since we are mutating its environment.

If we change it to a closure move , it is different:

let mut num = 5 ;

{

let mut sum_num = move | x: i32 | num + = x;

sum_num ( 5 );

}

assert_eq! ( 5 , num);

We get only 5 . Instead of taking a mutable loan on our number , we take property on a copy.

Another way to think about closures move is: they provide the closure with its own activation record. Without move , the closure can be associated with the activation record that created it, while a closure move is self-contained. Which means, for example, that you generally cannot return a non- move closure from a function.

But before talking about receiving closures as parameters and using them as return values, we should talk a little more about their implementation. Like a Rust system programming language it gives you a ton of control over what your code does, and closures are no different.

Implementation of Closures

Rust's implementation of closures is a little different than other languages. In Rust, closures are effectively an alternate syntax for traits. Before continuing, you will need to have read the chapter on traits as well as the chapter on trait objects .

Have you already read them? Excellent.

The key to understanding how closures work is kind of weird: Using () to call a function, like foo () , is an overload operator. Starting from this premise, everything else fits. At Rust we make use of the traits system to overload operators. Calling functions is no different. There are three traits that we can overload:

# mod foo {

pub trait Fn <Args>: FnMut <Args> {

extern "rust-call" fn call (& self, args: Args) -> Self :: Output;

}

pub trait FnMut <Args>: FnOnce <Args> {

extern "rust-call" fn call_mut (& mut self, args: Args) -> Self :: Output;

}

pub trait FnOnce <Args> {

type Output;

extern "rust-call" fn call_once (self, args: Args) -> Self :: Output;

}

#}

You will notice a few differences between these traits, but a big one is self : Fn receives & self , FnMut takes & mut self and FnOnce receives self . The above covers all three types of self through the usual method call syntax. But they have been separated into three traits, rather than just one. This gives us great control over the type of closures we can receive.

The syntax || {} is an alternate syntax for those three traits. Rust will generate a struct for the environment, impl the appropriate trait, and then make use of it.

Receiving closures as arguments

Now that we know that closures are traits, then we know how to accept and return closures: just like any other trait!

The above also means that we can choose between static or dynamic dispatch. First, let's create a function that receives something callable, executes a call on it, and then returns the result:

fn call_with_one <F> (some_closure: F) -> i32

where F: Fn ( i32 ) -> i32 {

some_closure ( 1 )

}

let answer = call_with_one (| x | x + 2 );

assert_eq! ( 3 , answer);

We pass our closure, | x | x + 2 , to call_with_one . calling_with_one does what it suggests: calls the closure, giving it 1 as an argument.

Let's examine the call_with_one signature in more detail:

fn call_with_one <F> (some_closure: F) -> i32

#    where F: Fn ( i32 ) -> i32 {

# some_closure ( 1 )}

We receive a parameter of type F . We also return an i32 . This part is not interesting. The following is:

# fn call_with_one <F> (some_closure: F) -> i32

where F: Fn ( i32 ) -> i32 {

# some_closure ( 1 )}

Because Fn is a trait, we can limit our generic with it. In this case, our closure receives an i32 and returns an i32 , that is why the generic limit we use is Fn (i32) -> i32 .

There is another key point here: because we are limiting a generic with a trait, the call will be monomorphized, and consequently, we will be doing static dispatch in the closure. That is super cool. In many languages, closures are inherently assigned from the mound, and will almost always involve dynamic dispatch. In Rust we can assign the environment of our closures from the stack, as well as dispatch the call statically. This occurs quite frequently with iterators and their adapters, which receive closures as arguments.

Of course, if we want dynamic dispatch, we can have it too. A trait object, as usual, handles this case:

fn call_with_one (some_closure: & Fn ( i32 ) -> i32 ) -> i32 {

some_closure ( 1 )

}

let answer = call_with_one (& | x | x + 2 );

assert_eq! ( 3 , answer);

We now receive a trait object, an & Fn . And we have to make a reference to our closure when we pass it to call_with_one , that is why we use & || .

Function pointers and closures

A function pointer is a kind of closure that has no environment. As a consequence, we can pass a function pointer to any function that receives a closure as an argument:

fn call_with_one (some_closure: & Fn ( i32 ) -> i32 ) -> i32 {

some_closure ( 1 )

}

fn sum_one (i: i32 ) -> i32 {

i + 1

}

let f = sum_one;

let answer = call_with_one (& f);

assert_eq! ( 2 , answer);

In the previous example we don't need the intermediate variable f strictly, the function name also works:

let answer = call_with_one (& sum_one);

Returning closures

It is very common for functional styled code to return closures in various situations. If you try to return a closure, you could make an error. At first it may seem strange, but later we will understand. You would probably try to return a closure from a function like this:

fn factory () -> (Fn (i32) -> i32) {

let num = 5;

| x | x + num

}

let f = factory ();

let answer = f (1);

assert_eq! (6, answer);

The above generates these long, but related errors:

error: the trait `core :: marker :: Sized` is not implemented for the type

`core :: ops :: Fn (i32) -> i32` [E0277]

fn factory () -> (Fn (i32) -> i32) {

^ ~~~~~~~~~~~~~~~

note: `core :: ops :: Fn (i32) -> i32` does not have a constant size known at compile-time

fn factory () -> (Fn (i32) -> i32) {

^ ~~~~~~~~~~~~~~~

error: the trait `core :: marker :: Sized` is not implemented for the type` core :: ops :: Fn (i32) -> i32` [E0277]

let f = factory ();

^

note: `core :: ops :: Fn (i32) -> i32` does not have a constant size known at compile-time

let f = factory ();

^

To return something from a function, Rust needs to know the size of the return type. But because Fn is a trait, it can be various things of various sizes: many types can implement Fn . An easy way to size something is by taking a reference to it, because references have a known size. Instead of the above we can write:

fn factory () -> & (Fn (i32) -> i32) {

let num = 5;

| x | x + num

}

let f = factory ();

let answer = f (1);

assert_eq! (6, answer);

But we get another error:

error: missing lifetime specifier [E0106]

fn factory () -> & (Fn (i32) -> i32) {

^ ~~~~~~~~~~~~~~~~

Well. Because we have a reference, we need to provide a lifetime. but our factory () function does not receive any arguments and therefore the avoidance of life times is not possible in this case. So what options do we have? Let's try 'static :

fn factory () -> & 'static (Fn (i32) -> i32) {

let num = 5;

| x | x + num

}

let f = factory ();

let answer = f (1);

assert_eq! (6, answer);

But we get another error:

error: mismatched types:

expected `& 'static core :: ops :: Fn (i32) -> i32`,

found `[closure @: 7: 9: 7:20]`

(expected & -ptr,

found closure) [E0308]

| x | x + num

^ ~~~~~~~~~~

The error tells us that we don't have a & 'static Fn (i32) -> i32 , but a [closure @ <anon>: 7: 9: 7:20] . Wait a minute, what?

Because our closure generates its own struct for the environment as well as an implementation for Fn , FnMut and FnOnce , these types are anonymous. They only exist for this closure. This is why Rust displays them as closure @ <anon> instead of some auto-generated name.

The error also talks about the return type being expected to be a reference, but what we are trying to return is not. Furthermore, we cannot directly assign an 'static' lifetime to an object. So we will take a different approach and return a 'trait object' by wrapping the Fn in a Box . The following almost works:

fn factory () -> Box i32> {

let num = 5;

Box :: new (| x | x + num)

}

# fn main () {

let f = factory ();

let answer = f (1);

assert_eq! (6, answer);

#}

There is one last problem:

error: closure may outlive the current function, but it borrows `num`,

which is owned by the current function [E0373]

Box :: new (| x | x + num)

^ ~~~~~~~~~~

Well, as we discussed earlier, closures borrow their environment. And in this case, our environment is based on a 5 assigned from the stack, the variable num . Due to this, the loan has the lifetime of the activation record. If this closure is returned, the function call could end, the activation record would disappear and our closure would be capturing a junk memory environment! With one last fix, we can make it work:

fn factory () -> Box < Fn ( i32 ) -> i32 > {

let num = 5 ;

Box :: new (move | x | x + num)

}

# fn main () {

let f = factory ();

let answer = f ( 1 );

assert_eq! ( 6 , answer);

#}

By making the internal closure a move Fn , we have created a new activation record for our closure. By wrapping it with a Box , we have provided it with a known size, allowing you to escape our activation record.